-- luacheck: globals RegisterStateDriver UnregisterStateDriver RegisterUnitWatch
local parentAddonName = "EnhanceQoL"
local addon = select(2, ...)

if _G[parentAddonName] then
	addon = _G[parentAddonName]
else
	error(parentAddonName .. " is not loaded")
end

addon.Aura = addon.Aura or {}
addon.Aura.UF = addon.Aura.UF or {}
local UF = addon.Aura.UF

UF.GroupFrames = UF.GroupFrames or {}
local GF = UF.GroupFrames
local EMPTY = {}
local getCfg
local isEditModeActive

local UFHelper = addon.Aura.UFHelper
local AuraUtil = UF.AuraUtil
local EditMode = addon.EditMode
local SettingType = EditMode and EditMode.lib and EditMode.lib.SettingType
local DispelOverlayOrientation = EnumUtil and EnumUtil.MakeEnum("VerticalTopToBottom", "VerticalBottomToTop", "HorizontalLeftToRight")

local PowerBarColor = PowerBarColor
local LSM = LibStub and LibStub("LibSharedMedia-3.0")
local LCG = LibStub and LibStub("LibCustomGlow-1.0", true)
local DISPEL_GLOW_KEY = "EQOL_DISPEL"

local GFH = UF.GroupFramesHelper
local Pixel = GFH and GFH.Pixel
local clampNumber = GFH.ClampNumber
GF._sharedEdit = {
	csm = GFH.CopySelectionMap,
	roleOpts = GFH.roleOptions,
	defaultRoleSel = GFH.DefaultRoleSelection,
	defaultSpecSel = GFH.DefaultSpecSelection,
	auraAnchorOpts = GFH.auraAnchorOptions,
	anchorOpts9 = GFH.anchorOptions9 or GFH.auraAnchorOptions,
	textModeOpts = GFH.textModeOptions,
	healthTextModeOpts = GFH.healthTextModeOptions or GFH.textModeOptions,
	delimiterOpts = GFH.delimiterOptions,
	outlineOpts = GFH.outlineOptions,
}
local ensureAuraConfig = GFH.EnsureAuraConfig
local L = LibStub("AceLocale-3.0"):GetLocale("EnhanceQoL")
GF._sharedEdit.dataBarTextModeOpts = {
	{ value = "NAME", label = NAME or L["Name"] or "Name", text = NAME or L["Name"] or "Name" },
	{ value = "LEVEL", label = LEVEL or L["Level"] or "Level", text = LEVEL or L["Level"] or "Level" },
}
do
	local seen = { NAME = true, LEVEL = true }
	local function addTextOptions(options)
		for _, option in ipairs(options or EMPTY) do
			if option.value and not seen[option.value] then
				seen[option.value] = true
				GF._sharedEdit.dataBarTextModeOpts[#GF._sharedEdit.dataBarTextModeOpts + 1] = option
			end
		end
	end
	addTextOptions(GF._sharedEdit.textModeOpts)
	addTextOptions(GF._sharedEdit.healthTextModeOpts)
end
local GROUP_DEBUFF_FILTER_ALL = "ALL"
local GROUP_DEBUFF_FILTER_CROWD_CONTROL = "CROWD_CONTROL"
local GROUP_DEBUFF_FILTER_IMPORTANT = "IMPORTANT"
local GROUP_DEBUFF_FILTER_RAID = "RAID"
local GROUP_DEBUFF_FILTER_RAID_IN_COMBAT = "RAID_IN_COMBAT"
GF.BLIZZARD_DISPEL_MODE_BY_ME = "BY_ME"
GF.BLIZZARD_DISPEL_MODE_ALL = "ALL"
GF.BLIZZARD_AURA_ORGANIZATION_LEGACY = "LEGACY"
GF.BLIZZARD_AURA_ORGANIZATION_TOP_BOTTOM = "BUFFS_TOP_DEBUFFS_BOTTOM"
GF.BLIZZARD_AURA_ORGANIZATION_RIGHT_LEFT = "BUFFS_RIGHT_DEBUFFS_LEFT"
local groupDebuffFilterOptions = {
	{ value = GROUP_DEBUFF_FILTER_ALL, label = L["UFGroupDebuffFilterAll"] or "All debuffs" },
	{ value = GROUP_DEBUFF_FILTER_RAID, label = L["UFGroupDebuffFilterRaid"] or "Raid debuffs" },
	{ value = GROUP_DEBUFF_FILTER_RAID_IN_COMBAT, label = L["UFGroupDebuffFilterRaidInCombat"] or "Raid in combat" },
	{ value = GROUP_DEBUFF_FILTER_CROWD_CONTROL, label = L["UFGroupDebuffFilterCrowdControl"] or "Crowd control" },
	{ value = "DISPEL", label = L["UFGroupDebuffFilterDispel"] or "Dispellable" },
	{ value = GROUP_DEBUFF_FILTER_IMPORTANT, label = L["UFGroupDebuffFilterImportant"] or "Important spells" },
}
GF.groupBuffFilterOptions = {
	{ value = "RAID_IN_COMBAT", label = L["UFGroupBuffFilterRaidInCombat"] or "Healer buffs" },
	{ value = "RAID", label = L["UFGroupBuffFilterRaid"] or "Helpful effects" },
}

GF.blizzardDispelIndicatorModeOptions = {
	{ value = GF.BLIZZARD_DISPEL_MODE_BY_ME, label = L["UFAuraRendererBlizzardDispelModeByMe"] or "Dispellable by me", text = L["UFAuraRendererBlizzardDispelModeByMe"] or "Dispellable by me" },
	{ value = GF.BLIZZARD_DISPEL_MODE_ALL, label = L["UFAuraRendererBlizzardDispelModeAll"] or "All", text = L["UFAuraRendererBlizzardDispelModeAll"] or "All" },
}

GF.blizzardAuraOrganizationOptions = {
	{ value = GF.BLIZZARD_AURA_ORGANIZATION_LEGACY, label = L["UFAuraRendererBlizzardOrganizationLegacy"] or "Legacy", text = L["UFAuraRendererBlizzardOrganizationLegacy"] or "Legacy" },
	{ value = GF.BLIZZARD_AURA_ORGANIZATION_TOP_BOTTOM, label = L["UFAuraRendererBlizzardOrganizationBuffsTopDebuffsBottom"] or "Buffs top, debuffs bottom", text = L["UFAuraRendererBlizzardOrganizationBuffsTopDebuffsBottom"] or "Buffs top, debuffs bottom" },
	{ value = GF.BLIZZARD_AURA_ORGANIZATION_RIGHT_LEFT, label = L["UFAuraRendererBlizzardOrganizationBuffsRightDebuffsLeft"] or "Buffs right, debuffs left", text = L["UFAuraRendererBlizzardOrganizationBuffsRightDebuffsLeft"] or "Buffs right, debuffs left" },
}

GF.splitRoleViewerRoleOptions = {
	{ value = "ALL", label = L["All"] or "All" },
	{ value = "TANK", label = TANK or "Tank" },
	{ value = "HEALER", label = HEALER or "Healer" },
	{ value = "DAMAGER", label = DAMAGER or "DPS" },
}

local function textureOptions() return GFH.TextureOptions(LSM) end
local function fontOptions() return GFH.FontOptions(LSM) end
local function normalizeFontStyleChoice(value, fallback)
	if addon.functions and addon.functions.NormalizeFontStyleChoice then return addon.functions.NormalizeFontStyleChoice(value, fallback, true) end
	if value ~= nil then return value end
	return fallback or "OUTLINE"
end
function GF.FontOptionsWithDefault() return fontOptions() end
function GF.GlobalFontConfigKey()
	if addon.functions and addon.functions.GetGlobalFontConfigKey then return addon.functions.GetGlobalFontConfigKey() end
	return "__EQOL_GLOBAL_FONT__"
end
function GF.DropdownOptionLabel(options, value, fallback)
	local list = type(options) == "function" and options() or options
	if type(list) == "table" then
		for _, option in ipairs(list) do
			if option.value == value then return option.label or option.text or fallback end
		end
	end
	return fallback
end
function GF.DropdownRadioGenerator(options)
	return function(_, root, data)
		local list = type(options) == "function" and options() or options
		if type(list) ~= "table" then return end
		for _, option in ipairs(list) do
			local label = option.label or option.text or tostring(option.value or "")
			root:CreateRadio(label, function() return data.get and data.get() == option.value end, function()
				if data.set then data.set(nil, option.value) end
				data.customDefaultText = label
				if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
			end)
		end
	end
end
local function requestEditModeSettingsRefresh()
	if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
end
function GF.MarkEditModeReloadRequired()
	addon.variables = addon.variables or {}
	addon.variables.requireReload = true
	GF._editModeReloadRequired = true
end
function GF.MarkBlizzardRendererRemovalReloadIfNeeded()
	if GF._editModeSampleAuras ~= false then return end
	GF.MarkEditModeReloadRequired()
end
function GF.ShowEditModeReloadIfRequired()
	if not GF._editModeReloadRequired then return end
	GF._editModeReloadRequired = nil
	addon.variables = addon.variables or {}
	addon.variables.requireReload = true
	if addon.functions and addon.functions.checkReloadFrame then addon.functions.checkReloadFrame() end
end
function GF.NormalizeAuraIconShape(value, fallback)
	if AuraUtil and AuraUtil.NormalizeIconShape then return AuraUtil.NormalizeIconShape(value, fallback or "DEFAULT") end
	if addon.IconShape and addon.IconShape.Normalize then return addon.IconShape.Normalize(value, fallback or "DEFAULT") end
	return value or fallback or "DEFAULT"
end

function GF.IsAuraIconShapeBackdropCompatible(shape)
	if AuraUtil and AuraUtil.IsIconShapeBackdropBorderCompatible then return AuraUtil.IsIconShapeBackdropBorderCompatible(shape) end
	if addon.IconShape and addon.IconShape.IsBackdropBorderCompatible then return addon.IconShape.IsBackdropBorderCompatible(shape) end
	shape = GF.NormalizeAuraIconShape(shape, "DEFAULT")
	return shape == "DEFAULT" or shape == "SQUARE"
end

function GF.NormalizeAuraBorderTexture(value, fallback, shape)
	if addon.IconShape and addon.IconShape.NormalizeBorder then return addon.IconShape.NormalizeBorder(value, fallback or "DEFAULT", shape, { allowNone = true }) end
	return value or fallback or "DEFAULT"
end

local function borderOptions(shape)
	local names = addon.functions and addon.functions.GetLSMMediaNames and addon.functions.GetLSMMediaNames("border") or {}
	local hash = addon.functions and addon.functions.GetLSMMediaHash and addon.functions.GetLSMMediaHash("border") or {}
	if addon.IconShape and addon.IconShape.GetBorderOptions then
		return addon.IconShape.GetBorderOptions(L, shape, {
			defaultOptions = {
				{ value = "DEFAULT", label = "Default (Border)" },
				{ value = "SOLID", label = "Solid" },
			},
			lsmNames = names,
			lsmHash = hash,
			includeNone = true,
			noneLabel = _G.NONE or "None",
		})
	end
	local list = {}
	local seen = {}
	local function add(value, label)
		local lv = tostring(value or ""):lower()
		if lv == "" or seen[lv] then return end
		seen[lv] = true
		list[#list + 1] = { value = value, label = label }
	end
	add("DEFAULT", "Default (Border)")
	add("SOLID", "Solid")
	for i = 1, #names do
		local name = names[i]
		local path = hash[name]
		if type(path) == "string" and path ~= "" then add(name, tostring(name)) end
	end
	return list
end

function GF.IsAuraBorderAdjustable(typeCfg, defaultCfg)
	local shape = GF.NormalizeAuraIconShape((typeCfg and typeCfg.iconShape) or (defaultCfg and defaultCfg.iconShape), "DEFAULT")
	local borderTexture = (typeCfg and typeCfg.borderTexture) or (defaultCfg and defaultCfg.borderTexture) or "DEFAULT"
	borderTexture = GF.NormalizeAuraBorderTexture(borderTexture, "DEFAULT", shape)
	if addon.IconShape and addon.IconShape.IsNoBorder and addon.IconShape.IsNoBorder(borderTexture) then return false end
	if not GF.IsAuraIconShapeBackdropCompatible(shape) then
		return addon.IconShape and addon.IconShape.SupportsBorderOffset and addon.IconShape.SupportsBorderOffset(borderTexture, shape) == true
	end
	return tostring(borderTexture or "DEFAULT"):upper() ~= "DEFAULT"
end

function GF.IsAuraBorderSizeAdjustable(typeCfg, defaultCfg)
	local shape = GF.NormalizeAuraIconShape((typeCfg and typeCfg.iconShape) or (defaultCfg and defaultCfg.iconShape), "DEFAULT")
	local borderTexture = (typeCfg and typeCfg.borderTexture) or (defaultCfg and defaultCfg.borderTexture) or "DEFAULT"
	borderTexture = GF.NormalizeAuraBorderTexture(borderTexture, "DEFAULT", shape)
	if addon.IconShape and addon.IconShape.IsNoBorder and addon.IconShape.IsNoBorder(borderTexture) then return false end
	if not GF.IsAuraIconShapeBackdropCompatible(shape) then
		return addon.IconShape and addon.IconShape.SupportsBorderSize and addon.IconShape.SupportsBorderSize(borderTexture, shape) == true
	end
	return tostring(borderTexture or "DEFAULT"):upper() ~= "DEFAULT"
end

local max = math.max
local min = math.min
local floor = math.floor
local hooksecurefunc = hooksecurefunc
function GF.ClampFrameLevel(level)
	level = tonumber(level) or 0
	level = floor(level + (level >= 0 and 0.5 or -0.5))
	if level < 0 then return 0 end
	if level > 65535 then return 65535 end
	return level
end
local BAR_TEX_INHERIT = "__PER_BAR__"
local AURA_FILTERS = GFH.AuraFilters
local FONT_DROPDOWN_SCROLL_HEIGHT = 220
local GROUP_FRAME_MAX_SPACING = 100
GF._FRAME_STRATA_INDEX = GF._FRAME_STRATA_INDEX or {
	BACKGROUND = true,
	LOW = true,
	MEDIUM = true,
	HIGH = true,
	DIALOG = true,
	FULLSCREEN = true,
	FULLSCREEN_DIALOG = true,
	TOOLTIP = true,
}
GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT = GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT
	or {
		{ value = "", label = DEFAULT or "Default" },
		{ value = "BACKGROUND", label = "BACKGROUND" },
		{ value = "LOW", label = "LOW" },
		{ value = "MEDIUM", label = "MEDIUM" },
		{ value = "HIGH", label = "HIGH" },
		{ value = "DIALOG", label = "DIALOG" },
		{ value = "FULLSCREEN", label = "FULLSCREEN" },
		{ value = "FULLSCREEN_DIALOG", label = "FULLSCREEN_DIALOG" },
		{ value = "TOOLTIP", label = "TOOLTIP" },
	}

function GF.NormalizeFrameStrataToken(value)
	if type(value) ~= "string" or value == "" then return nil end
	local token = string.upper(value)
	if token == "DEFAULT" then return nil end
	if GF._FRAME_STRATA_INDEX[token] then return token end
	return nil
end

function GF.SyncFrameLayerAbove(child, parent, offset, strata)
	if not (child and parent) then return end
	local targetStrata = GF.NormalizeFrameStrataToken(strata)
	if not targetStrata and parent.GetFrameStrata then targetStrata = parent:GetFrameStrata() end
	if child.SetFrameStrata and targetStrata and child:GetFrameStrata() ~= targetStrata then child:SetFrameStrata(targetStrata) end
	if child.SetFrameLevel and parent.GetFrameLevel then
		local targetLevel = GF.ClampFrameLevel((parent:GetFrameLevel() or 0) + (offset or 1))
		if child:GetFrameLevel() ~= targetLevel then child:SetFrameLevel(targetLevel) end
	end
end

function GF.EnsureTextOverlayLayer(st, key, parent)
	if not (st and parent) then return nil end
	local layer = st[key]
	if not layer then
		layer = CreateFrame("Frame", nil, parent)
		layer:EnableMouse(false)
		st[key] = layer
	end
	if layer.GetParent and layer:GetParent() ~= parent then layer:SetParent(parent) end
	layer:SetAllPoints(parent)
	return layer
end

function GF.ClampAuraCount(value, fallback, maxValue)
	local normalized = tonumber(value)
	if normalized == nil then return fallback end
	normalized = floor(normalized + 0.5)
	if normalized < 1 then normalized = 1 end
	if maxValue ~= nil and normalized > maxValue then normalized = maxValue end
	return normalized
end

GFH.COLOR_INCOMING_HEAL_DEFAULT = GFH.COLOR_INCOMING_HEAL_DEFAULT or { 0.2, 0.85, 0.35, 0.45 }

local function formatSliderDecimal(value)
	local text = string.format("%.2f", tonumber(value) or 0)
	text = text:gsub("(%..-)0+$", "%1")
	text = text:gsub("%.$", "")
	return text
end

function GF.FormatOverlayHeight(value)
	if (tonumber(value) or 0) <= 0 then return L["Max"] or "Max" end
	return tostring(floor((tonumber(value) or 0) + 0.5))
end

function GF.SetStatusBarValue(bar, value, smooth, forceImmediate)
	if not bar or value == nil then return end
	local pixelHelper = GFH and GFH.Pixel
	if pixelHelper and pixelHelper.SetStatusBarValue then
		pixelHelper.SetStatusBarValue(bar, value, smooth, forceImmediate)
		return
	end
	if smooth and Enum and Enum.StatusBarInterpolation and Enum.StatusBarInterpolation.ExponentialEaseOut then
		bar:SetValue(value, Enum.StatusBarInterpolation.ExponentialEaseOut)
	else
		bar:SetValue(value)
	end
end

function GF.GetDynamicContentScaleAmount(cfg)
	if type(cfg) ~= "table" then return 0 end
	local value = cfg.dynamicScaleMin
	if value == nil then
		if cfg.dynamicContentScale == true then return 1 end
		return 0
	end
	return clampNumber(tonumber(value) or 0, 0, 1, 0)
end

function GF.IsDynamicContentScaleEnabled(kind, cfg) return kind == "raid" and GF.GetDynamicContentScaleAmount(cfg) > 0 end

function GF.GetDynamicContentScale(self, cfg)
	if not self then return 1 end
	local kind = self._eqolGroupKind or "party"
	cfg = cfg or self._eqolCfg or getCfg(kind)
	local amount = GF.GetDynamicContentScaleAmount(cfg)
	if kind ~= "raid" then return 1 end
	local parent = self.GetParent and self:GetParent() or nil
	local fitScale = tonumber(self._eqolFitScale)
	if not (fitScale and fitScale > 0 and fitScale < 1) then fitScale = tonumber(parent and parent._eqolFitScale) end
	if fitScale and fitScale > 0 and fitScale < 1 then return fitScale + ((1 - fitScale) * amount) end
	if amount <= 0 then return 1 end
	local inverseScale = 1
	local ownScale = self.GetScale and self:GetScale() or 1
	ownScale = tonumber(ownScale) or 1
	local parentScale = parent and parent.GetScale and parent:GetScale() or 1
	parentScale = tonumber(parentScale) or 1
	if ownScale > 0 and ownScale < 1 then
		inverseScale = 1 / ownScale
	elseif parentScale > 0 and parentScale < 1 then
		inverseScale = 1 / parentScale
	end
	if inverseScale <= 1 then return 1 end
	return 1 + ((inverseScale - 1) * amount)
end

function GF.ScaleContentValue(self, value, cfg, minimum)
	local scaled = tonumber(value)
	if not scaled then return value end
	scaled = scaled * GF.GetDynamicContentScale(self, cfg)
	if minimum and scaled < minimum then scaled = minimum end
	return scaled
end

function GF.ScaleOffset(offset, factor)
	if factor == nil or factor == 1 or type(offset) ~= "table" then return offset end
	return {
		x = (tonumber(offset.x) or 0) * factor,
		y = (tonumber(offset.y) or 0) * factor,
	}
end

function GF.GetScaledBarTextConfig(cfgSection, factor)
	if factor == nil or factor == 1 or type(cfgSection) ~= "table" then return cfgSection end
	return {
		offsetLeft = GF.ScaleOffset(cfgSection.offsetLeft, factor),
		offsetCenter = GF.ScaleOffset(cfgSection.offsetCenter, factor),
		offsetRight = GF.ScaleOffset(cfgSection.offsetRight, factor),
	}
end

function GF.ApplyScaledFont(self, fontString, font, size, outline, cfg)
	if not (fontString and UFHelper and UFHelper.applyFont) then return end
	UFHelper.applyFont(fontString, font, GF.ScaleContentValue(self, size, cfg, 1), outline)
end

function GF.GetScaledPrivateAuraConfig(self, cfg)
	if type(cfg) ~= "table" then return cfg end
	local factor = GF.GetDynamicContentScale(self, self and self._eqolCfg or nil)
	if factor == 1 then return cfg end

	local scaled = {}
	for key, value in pairs(cfg) do
		scaled[key] = value
	end

	local icon = {}
	for key, value in pairs(cfg.icon or EMPTY) do
		icon[key] = value
	end
	icon.size = GF.ScaleContentValue(self, icon.size or 24, nil, 1)
	icon.minSize = GF.ScaleContentValue(self, icon.minSize or 4, nil, 1)
	icon.maxSize = GF.ScaleContentValue(self, icon.maxSize or 100, nil, 1)
	icon.offset = (tonumber(icon.offset or icon.spacing or 2) or 0) * factor
	if icon.spacing ~= nil then icon.spacing = (tonumber(icon.spacing) or 0) * factor end
	if icon.borderScale ~= nil then icon.borderScale = (tonumber(icon.borderScale) or 0) * factor end
	scaled.icon = icon

	local parentCfg = {}
	for key, value in pairs(cfg.parent or EMPTY) do
		parentCfg[key] = value
	end
	parentCfg.offsetX = (tonumber(parentCfg.offsetX) or 0) * factor
	parentCfg.offsetY = (tonumber(parentCfg.offsetY) or 0) * factor
	scaled.parent = parentCfg

	local durationCfg = {}
	for key, value in pairs(cfg.duration or EMPTY) do
		durationCfg[key] = value
	end
	durationCfg.offsetX = (tonumber(durationCfg.offsetX) or 0) * factor
	durationCfg.offsetY = (tonumber(durationCfg.offsetY) or 0) * factor
	scaled.duration = durationCfg

	return scaled
end

function GF.NormalizeBuffHelpfulFilterMode(value)
	value = tostring(value or ""):upper()
	if value == "RAID" then return "RAID" end
	return "RAID_IN_COMBAT"
end

function GF.GetBuffHelpfulFilter(ac)
	local selection = ac and ac.buff and ac.buff.filterSelection
	if type(selection) == "table" then
		if not GFH.SelectionHasAny(selection) then return nil end
		local filters = {}
		if GFH.SelectionContains(selection, "RAID_IN_COMBAT") then filters[#filters + 1] = (AURA_FILTERS and AURA_FILTERS.helpful) or "HELPFUL|INCLUDE_NAME_PLATE_ONLY|RAID_IN_COMBAT|PLAYER" end
		if GFH.SelectionContains(selection, "RAID") then filters[#filters + 1] = "HELPFUL|INCLUDE_NAME_PLATE_ONLY|RAID|PLAYER" end
		if #filters == 0 then return nil end
		if #filters == 1 then return filters[1] end
		return filters
	end
	local mode = GF.NormalizeBuffHelpfulFilterMode(ac and ac.buff and ac.buff.helpfulFilterMode)
	if mode == "RAID" then return "HELPFUL|INCLUDE_NAME_PLATE_ONLY|RAID|PLAYER" end
	return (AURA_FILTERS and AURA_FILTERS.helpful) or "HELPFUL|INCLUDE_NAME_PLATE_ONLY|RAID_IN_COMBAT|PLAYER"
end

function GF.DefaultGroupBuffSelection() return { RAID_IN_COMBAT = true } end
local function defaultGroupDebuffSelection() return { [GROUP_DEBUFF_FILTER_ALL] = true } end

function GF.GetGroupBuffFilterSelection(typeCfg)
	if type(typeCfg) ~= "table" then return nil end
	local selection = typeCfg.filterSelection
	if type(selection) ~= "table" then return nil end
	return selection
end

function GF.IsGroupBuffFilterSelected(typeCfg, value)
	local selection = GF.GetGroupBuffFilterSelection(typeCfg)
	if selection == nil then return value == "RAID_IN_COMBAT" end
	return GFH.SelectionContains(selection, value)
end

function GF.SetGroupBuffFilterSelected(typeCfg, value, state)
	if type(typeCfg) ~= "table" or value == nil then return {} end
	local selection = typeCfg.filterSelection
	if type(selection) ~= "table" then
		selection = {}
		typeCfg.filterSelection = selection
	end

	if state then
		selection[value] = true
	else
		selection[value] = nil
	end
	return selection
end

local function getGroupDebuffFilterSelection(typeCfg)
	if type(typeCfg) ~= "table" then return nil end
	local selection = typeCfg.filterSelection
	if type(selection) ~= "table" then return nil end
	return selection
end

local function isGroupDebuffFilterSelected(typeCfg, value)
	local selection = getGroupDebuffFilterSelection(typeCfg)
	if selection == nil then return value == GROUP_DEBUFF_FILTER_ALL end
	if value == GROUP_DEBUFF_FILTER_ALL then return GFH.SelectionContains(selection, GROUP_DEBUFF_FILTER_ALL) end
	return GFH.SelectionContains(selection, value)
end

local function setGroupDebuffFilterSelected(typeCfg, value, state)
	if type(typeCfg) ~= "table" or value == nil then return {} end
	local selection = typeCfg.filterSelection
	if type(selection) ~= "table" then
		selection = {}
		typeCfg.filterSelection = selection
	end

	if value == GROUP_DEBUFF_FILTER_ALL then
		if state then
			for key in pairs(selection) do
				selection[key] = nil
			end
			selection[GROUP_DEBUFF_FILTER_ALL] = true
		else
			selection[GROUP_DEBUFF_FILTER_ALL] = nil
		end
		return selection
	end

	selection[GROUP_DEBUFF_FILTER_ALL] = nil
	if state then
		selection[value] = true
	else
		selection[value] = nil
	end
	return selection
end

local function getGroupDebuffMatchFilter(typeCfg)
	local selection = getGroupDebuffFilterSelection(typeCfg)
	if selection == nil then return AURA_FILTERS.harmful end
	if not GFH.SelectionHasAny(selection) then return nil end
	if GFH.SelectionContains(selection, GROUP_DEBUFF_FILTER_ALL) then return AURA_FILTERS.harmful end

	local filters = {}
	if GFH.SelectionContains(selection, GROUP_DEBUFF_FILTER_RAID) and AURA_FILTERS.harmfulRaid then filters[#filters + 1] = AURA_FILTERS.harmfulRaid end
	if GFH.SelectionContains(selection, GROUP_DEBUFF_FILTER_RAID_IN_COMBAT) and AURA_FILTERS.harmfulRaidInCombat then filters[#filters + 1] = AURA_FILTERS.harmfulRaidInCombat end
	if GFH.SelectionContains(selection, GROUP_DEBUFF_FILTER_CROWD_CONTROL) and AURA_FILTERS.harmfulCrowdControl then filters[#filters + 1] = AURA_FILTERS.harmfulCrowdControl end
	if GFH.SelectionContains(selection, "DISPEL") and AURA_FILTERS.dispellable then filters[#filters + 1] = AURA_FILTERS.dispellable end
	if GFH.SelectionContains(selection, GROUP_DEBUFF_FILTER_IMPORTANT) and AURA_FILTERS.harmfulImportant then filters[#filters + 1] = AURA_FILTERS.harmfulImportant end
	if #filters == 0 then return nil end
	return filters
end

local function exportGroupDebuffSelection(typeCfg)
	local selection = typeCfg and typeCfg.filterSelection
	if type(selection) == "table" then return GF._sharedEdit.csm(selection) end
	return defaultGroupDebuffSelection()
end

function GF.ExportGroupBuffSelection(typeCfg)
	local selection = typeCfg and typeCfg.filterSelection
	if type(selection) == "table" then return GF._sharedEdit.csm(selection) end
	local mode = GF.NormalizeBuffHelpfulFilterMode(typeCfg and typeCfg.helpfulFilterMode)
	if mode == "RAID" then return { RAID = true } end
	return GF.DefaultGroupBuffSelection()
end

function GF.CaptureGroupAuraSlotResults(...)
	GF._groupAuraSlotResultBuffer = GF._groupAuraSlotResultBuffer or {}
	local buffer = GF._groupAuraSlotResultBuffer
	local previousCount = GF._groupAuraSlotResultCount or 0
	local count = select("#", ...)
	for i = 1, count do
		buffer[i] = select(i, ...)
	end
	if previousCount > count then
		for i = count + 1, previousCount do
			buffer[i] = nil
		end
	end
	GF._groupAuraSlotResultCount = count
	return buffer, count
end

local function queryAuraSlots(unit, filter, maxCount)
	if not filter then return nil, 0 end
	if maxCount then return GF.CaptureGroupAuraSlotResults(C_UnitAuras.GetAuraSlots(unit, filter, maxCount)) end
	return GF.CaptureGroupAuraSlotResults(C_UnitAuras.GetAuraSlots(unit, filter))
end

GF._previewSamples = GFH.PREVIEW_SAMPLES or { party = {}, raid = {}, mt = {}, ma = {} }
GF._groupNumberFormatCache = {
	opts = GFH.GROUP_NUMBER_FORMAT_OPTIONS or {},
	tokens = {},
	byText = {},
	labelByValue = {},
}
do
	for _, option in ipairs(GF._groupNumberFormatCache.opts) do
		if option then
			local value = option.value
			if value ~= nil then
				GF._groupNumberFormatCache.tokens[value] = true
				if GF._groupNumberFormatCache.labelByValue[value] == nil then GF._groupNumberFormatCache.labelByValue[value] = option.label or option.text or tostring(value) end
			end
			if option.text ~= nil then GF._groupNumberFormatCache.byText[option.text] = value end
			if option.label ~= nil then GF._groupNumberFormatCache.byText[option.label] = value end
		end
	end
end

local function normalizeGroupNumberFormat(format)
	if format == nil then return nil end
	if GF._groupNumberFormatCache.tokens[format] then return format end
	local text = tostring(format)
	local mapped = GF._groupNumberFormatCache.byText[text]
	if mapped then return mapped end
	local upper = text:upper()
	if GF._groupNumberFormatCache.tokens[upper] then return upper end
	mapped = GF._groupNumberFormatCache.byText[upper]
	if mapped then return mapped end
	return text
end

local function hideDispelTint(st)
	if not (st and st.dispelTint) then return end
	if st._dispelTintShown == false then return end
	st._dispelTintShown = false
	st.dispelTint:Hide()
end
local function applyDispelTint(st, r, g, b, alpha, fr, fg, fb, bgAlpha)
	if not (st and st.dispelTint) then return end
	st._dispelTintShown = true

	local bg = st.dispelTint.Background
	if bg then
		if bg.SetColorTexture then
			bg:SetColorTexture(fr, fg, fb, 1)
		elseif bg.SetVertexColor then
			bg:SetVertexColor(fr, fg, fb, 1)
		end
		if bg.SetAlpha then bg:SetAlpha(bgAlpha) end
		if bg.SetShown then bg:SetShown(bgAlpha > 0) end
	end
	local grad = st.dispelTint.Gradient
	if grad then grad:SetVertexColor(r, g, b, alpha) end
	local border = st.dispelTint.Border
	if border then border:SetVertexColor(r, g, b, alpha) end
	if st.dispelTint.SetAlpha then st.dispelTint:SetAlpha(1) end
	st.dispelTint:Show()
end

local function resolveBorderTexture(key)
	if UFHelper and UFHelper.resolveBorderTexture then return UFHelper.resolveBorderTexture(key) end
	if type(key) == "string" and key:upper() == "SOLID" then return "Interface\\Buttons\\WHITE8x8" end
	if not key or key == "" or key == "DEFAULT" then return "Interface\\Buttons\\WHITE8x8" end
	if LSM then
		local tex = LSM:Fetch("border", key)
		if tex and tex ~= "" then return tex end
	end
	return key
end

local function ensureBorderFrame(frame, borderCfg)
	if not frame then return nil end
	local border = frame._ufBorder
	if not border then
		border = CreateFrame("Frame", nil, frame, "BackdropTemplate")
		border:EnableMouse(false)
		frame._ufBorder = border
	end
	local targetStrata = frame:GetFrameStrata()
	local strata = GF.NormalizeFrameStrataToken(borderCfg and borderCfg.strata)
	if strata then targetStrata = strata end
	border:SetFrameStrata(targetStrata)
	local baseLevel = frame:GetFrameLevel() or 0
	local levelOffset = clampNumber(tonumber(borderCfg and borderCfg.frameLevelOffset), -20, 1000, 3)
	levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
	local borderLevel = GF.ClampFrameLevel(baseLevel + levelOffset)
	border:SetFrameLevel(borderLevel)
	border:ClearAllPoints()
	border:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
	border:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
	return border
end

local function setBackdrop(frame, borderCfg, preserveFrameBackdrop)
	if not frame then return end
	if borderCfg and borderCfg.enabled then
		if frame.SetBackdrop and not preserveFrameBackdrop then frame:SetBackdrop(nil) end
		local borderFrame = ensureBorderFrame(frame, borderCfg)
		if not borderFrame then return end
		local color = borderCfg.color or { 0, 0, 0, 0.8 }
		local edgeSize = tonumber(borderCfg.edgeSize) or 1
		if edgeSize < 1 then edgeSize = 1 end
		local offset = borderCfg.offset
		if offset == nil then offset = borderCfg.inset end
		if offset == nil then offset = edgeSize end
		offset = max(0, tonumber(offset) or 0)
		local insetVal = borderCfg.inset
		if insetVal == nil then insetVal = edgeSize end
		insetVal = max(0, tonumber(insetVal) or edgeSize)
		if Pixel and Pixel.Round then
			edgeSize = Pixel.Round(edgeSize, frame, 1)
			offset = Pixel.Round(offset, frame)
			insetVal = Pixel.Round(insetVal, frame, 1)
		end
		local edgeFile = (UFHelper and UFHelper.resolveBorderTexture and UFHelper.resolveBorderTexture(borderCfg.texture)) or "Interface\\Buttons\\WHITE8x8"
		borderFrame:ClearAllPoints()
		borderFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", -offset, offset)
		borderFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", offset, -offset)
		borderFrame:SetBackdrop({
			bgFile = "Interface\\Buttons\\WHITE8x8",
			edgeFile = edgeFile,
			tile = false,
			edgeSize = edgeSize,
			insets = { left = insetVal, right = insetVal, top = insetVal, bottom = insetVal },
		})
		borderFrame:SetBackdropColor(0, 0, 0, 0)
		borderFrame:SetBackdropBorderColor(color[1] or 0, color[2] or 0, color[3] or 0, color[4] or 1)
		borderFrame:Show()
	else
		if frame.SetBackdrop and not preserveFrameBackdrop then frame:SetBackdrop(nil) end
		local borderFrame = frame._ufBorder
		if borderFrame then
			borderFrame:SetBackdrop(nil)
			borderFrame:Hide()
		end
	end
end

function GF.EnsureGroupBorderFrame(kind, anchor)
	if kind ~= "party" or not anchor then return nil end
	GF.groupBorders = GF.groupBorders or {}
	local border = GF.groupBorders[kind]
	if not border then
		local parent = anchor:GetParent() or _G.PetBattleFrameHider or UIParent
		border = CreateFrame("Frame", "EQOLUFPartyGroupBorder", parent, "BackdropTemplate")
		border:EnableMouse(false)
		border:Hide()
		GF.groupBorders[kind] = border
	elseif border.GetParent and anchor.GetParent and border:GetParent() ~= anchor:GetParent() then
		border:SetParent(anchor:GetParent())
	end
	return border
end

function GF.HideGroupBorder(kind)
	local border = GF.groupBorders and GF.groupBorders[kind]
	if not border then return end
	if UnregisterStateDriver then UnregisterStateDriver(border, "visibility") end
	border:SetBackdrop(nil)
	if border._eqolBackdrop then
		border._eqolBackdrop:SetBackdrop(nil)
		border._eqolBackdrop:Hide()
	end
	border:Hide()
end

function GF.GetPartyGroupBorderCount(cfg)
	if not cfg then return 0 end
	if isEditModeActive and isEditModeActive() then
		if GF._previewActive and GF._previewActive.party then
			local sampleCount = GF._previewSampleCount and GF._previewSampleCount.party
			if sampleCount and sampleCount > 0 then return min(sampleCount, 5) end
			local samples = GF._previewSamples and GF._previewSamples.party
			return min((samples and #samples) or 5, 5)
		end
		return 5
	end
	if IsInRaid and IsInRaid() then return 0 end
	if IsInGroup and IsInGroup() then
		local count = (GetNumSubgroupMembers and GetNumSubgroupMembers()) or 0
		if cfg.showPlayer then count = count + 1 end
		if count < 1 then count = 1 end
		return min(count, 5)
	end
	if cfg.showSolo then return 1 end
	return 0
end

function GF.SetGroupBorderFrameBackdrop(border, borderCfg)
	if not border then return end
	local backdrop = border._eqolBackdrop
	if not backdrop then
		backdrop = CreateFrame("Frame", nil, border, "BackdropTemplate")
		backdrop:EnableMouse(false)
		border._eqolBackdrop = backdrop
	end
	local color = borderCfg.color or { 1, 1, 1, 1 }
	local edgeSize = tonumber(borderCfg.edgeSize) or 1
	if edgeSize < 1 then edgeSize = 1 end
	local offset = borderCfg.offset
	if offset == nil then offset = borderCfg.inset end
	if offset == nil then offset = edgeSize end
	offset = max(0, tonumber(offset) or 0)
	local insetVal = borderCfg.inset
	if insetVal == nil then insetVal = edgeSize end
	insetVal = max(0, tonumber(insetVal) or edgeSize)
	local edgeFile = (UFHelper and UFHelper.resolveBorderTexture and UFHelper.resolveBorderTexture(borderCfg.texture)) or "Interface\\Buttons\\WHITE8x8"

	backdrop:ClearAllPoints()
	backdrop:SetPoint("TOPLEFT", border, "TOPLEFT", -offset, offset)
	backdrop:SetPoint("BOTTOMRIGHT", border, "BOTTOMRIGHT", offset, -offset)
	backdrop:SetBackdrop({
		bgFile = "Interface\\Buttons\\WHITE8x8",
		edgeFile = edgeFile,
		tile = false,
		edgeSize = edgeSize,
		insets = { left = insetVal, right = insetVal, top = insetVal, bottom = insetVal },
	})
	backdrop:SetBackdropColor(0, 0, 0, 0)
	backdrop:SetBackdropBorderColor(color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1)
	backdrop:Show()
end

function GF:ApplyPartyGroupBorder(cfg, header, anchor, growth, scale, spacing, unitW, unitH, startPoint, relativePoint, offsetX, offsetY)
	local borderCfg = cfg and cfg.groupBorder
	if not (borderCfg and borderCfg.enabled == true and header and anchor) then
		GF.HideGroupBorder("party")
		return
	end
	local count = GF.GetPartyGroupBorderCount(cfg)
	if count <= 0 then
		GF.HideGroupBorder("party")
		return
	end

	local border = GF.EnsureGroupBorderFrame("party", anchor)
	if not border then return end

	local targetStrata = anchor:GetFrameStrata()
	local strata = GF.NormalizeFrameStrataToken(borderCfg.strata)
	if strata then targetStrata = strata end
	if border.SetFrameStrata and targetStrata then border:SetFrameStrata(targetStrata) end

	local levelOffset = clampNumber(tonumber(borderCfg.frameLevelOffset), -20, 1000, 3)
	levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
	local level = GF.ClampFrameLevel((anchor:GetFrameLevel() or 1) + levelOffset)
	if border.SetFrameLevel then border:SetFrameLevel(level) end

	local totalW, totalH
	if growth == "RIGHT" or growth == "LEFT" then
		totalW = unitW * count + spacing * max(0, count - 1)
		totalH = unitH
	else
		totalW = unitW
		totalH = unitH * count + spacing * max(0, count - 1)
	end
	if Pixel and Pixel.RoundEven then
		totalW = Pixel.RoundEven(totalW, scale, 2)
		totalH = Pixel.RoundEven(totalH, scale, 2)
	elseif GFH and GFH.RoundToPixel then
		totalW = GFH.RoundToPixel(totalW * 0.5, scale) * 2
		totalH = GFH.RoundToPixel(totalH * 0.5, scale) * 2
	end
	if Pixel and Pixel.SetSize then
		Pixel.SetSize(border, totalW, totalH)
	else
		border:SetSize(totalW, totalH)
	end

	GF.SetGroupBorderFrameBackdrop(border, borderCfg)
	border:ClearAllPoints()
	if Pixel and Pixel.SetPoint then
		Pixel.SetPoint(border, startPoint, anchor, relativePoint, offsetX, offsetY)
	else
		border:SetPoint(startPoint, anchor, relativePoint, offsetX, offsetY)
	end
	if RegisterStateDriver then
		if UnregisterStateDriver then UnregisterStateDriver(border, "visibility") end
		RegisterStateDriver(border, "visibility", header._eqolVisibilityCond or "hide")
	else
		border:Show()
	end
end

local function ensureHighlightFrame(st, key)
	if not (st and st.barGroup) then return nil end
	st._highlightFrames = st._highlightFrames or {}
	local frame = st._highlightFrames[key]
	local parent = st.layoutAnchor or st.frame or st.barGroup
	if not frame then
		frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
		frame:EnableMouse(false)
		st._highlightFrames[key] = frame
	elseif parent and frame.GetParent and frame:GetParent() ~= parent then
		frame:SetParent(parent)
	end
	return frame
end

local function buildHighlightConfig(cfg, def, key)
	local hcfg = (cfg and cfg[key]) or {}
	local hdef = (def and def[key]) or {}
	local enabled = hcfg.enabled
	if enabled == nil then enabled = hdef.enabled end
	if enabled ~= true then return nil end
	local texture = hcfg.texture or hdef.texture or "DEFAULT"
	local size = tonumber(hcfg.size or hdef.size) or 1
	if size < 1 then size = 1 end
	local color = hcfg.color
	if type(color) ~= "table" then color = hdef.color end
	if type(color) ~= "table" then color = GFH.COLOR_WHITE end
	local offset = hcfg.offset
	if offset == nil then offset = hdef.offset end
	offset = tonumber(offset) or 0
	local layer = tostring(hcfg.layer or hdef.layer or "ABOVE_BORDER"):upper()
	if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
	local strata = GF.NormalizeFrameStrataToken(hcfg.strata)
	if strata == nil then strata = GF.NormalizeFrameStrataToken(hdef.strata) end
	local frameLevelOffset = hcfg.frameLevelOffset
	if frameLevelOffset == nil then frameLevelOffset = hdef.frameLevelOffset end
	frameLevelOffset = tonumber(frameLevelOffset)
	local mode = hcfg.mode
	if mode == nil then mode = hdef.mode end
	local sample = hcfg.sample
	if sample == nil then sample = hdef.sample end
	return {
		enabled = true,
		texture = texture,
		size = size,
		color = color,
		offset = offset,
		layer = layer,
		strata = strata,
		frameLevelOffset = frameLevelOffset,
		mode = mode,
		sample = sample == true,
	}
end

local function applyHighlightStyle(st, cfg, key)
	if not st then return end
	local frame = st._highlightFrames and st._highlightFrames[key]
	if not cfg or cfg.enabled ~= true then
		if frame then
			if frame.SetBackdrop then frame:SetBackdrop(nil) end
			frame:Hide()
		end
		return
	end
	frame = ensureHighlightFrame(st, key)
	if not frame then return end
	local targetStrata = GF.NormalizeFrameStrataToken(cfg.strata)
	if not targetStrata and st.barGroup and st.barGroup.GetFrameStrata then targetStrata = st.barGroup:GetFrameStrata() end
	if frame.SetFrameStrata and targetStrata and frame:GetFrameStrata() ~= targetStrata then frame:SetFrameStrata(targetStrata) end
	if frame.SetFrameLevel and st.barGroup and st.barGroup.GetFrameLevel then
		local baseLevel = st.barGroup:GetFrameLevel() or 0
		local layer = tostring(cfg.layer or "ABOVE_BORDER"):upper()
		local levelOffset
		if layer == "BEHIND_BORDER" then
			levelOffset = 2
		else
			levelOffset = 4
		end
		local hasExplicitFrameLevel = cfg.frameLevelOffset ~= nil
		if hasExplicitFrameLevel then levelOffset = cfg.frameLevelOffset end
		levelOffset = clampNumber(levelOffset, -20, 1000, 4)
		levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
		if key == "aggro" then levelOffset = levelOffset + 1 end
		local targetLevel = GF.ClampFrameLevel(baseLevel + levelOffset)
		local border = st.barGroup._ufBorder
		if
			not hasExplicitFrameLevel
			and layer ~= "BEHIND_BORDER"
			and border
			and border.GetFrameLevel
			and border.GetFrameStrata
			and frame.GetFrameStrata
			and border:GetFrameStrata() == frame:GetFrameStrata()
		then
			local borderLevel = border:GetFrameLevel()
			if borderLevel and targetLevel <= borderLevel then targetLevel = GF.ClampFrameLevel(borderLevel + 1) end
		end
		frame:SetFrameLevel(targetLevel)
	end
	local size = cfg.size or 1
	if size < 1 then size = 1 end
	local offset = cfg.offset or 0
	if Pixel and Pixel.Round then
		size = Pixel.Round(size, st.barGroup, 1)
		offset = Pixel.Round(offset, st.barGroup)
	end
	frame:SetBackdrop({
		bgFile = "Interface\\Buttons\\WHITE8x8",
		edgeFile = resolveBorderTexture(cfg.texture),
		tile = false,
		edgeSize = size,
		insets = { left = size, right = size, top = size, bottom = size },
	})
	frame:SetBackdropColor(0, 0, 0, 0)
	local color = cfg.color or GFH.COLOR_WHITE
	frame:SetBackdropBorderColor(color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1)
	frame:ClearAllPoints()
	frame:SetPoint("TOPLEFT", st.barGroup, "TOPLEFT", -offset, offset)
	frame:SetPoint("BOTTOMRIGHT", st.barGroup, "BOTTOMRIGHT", offset, -offset)
	frame:Hide()
end

local function applyBarBackdrop(bar, cfg, options)
	if not bar then return end
	cfg = cfg or {}
	options = options or {}
	local bd = cfg.backdrop or {}
	local clampToFill = options.clampToFill == true
	local enabled = bd.enabled ~= false
	local col = bd.color or { 0, 0, 0, 0.6 }
	local r = col[1] or 0
	local g = col[2] or 0
	local b = col[3] or 0
	local a = col[4] or 0.6
	local currentStatusTex = (clampToFill and bar.GetStatusBarTexture and bar:GetStatusBarTexture()) or nil
	local backdropTextureKey = bd.texture
	local inheritedBackdropTexture = backdropTextureKey == nil or backdropTextureKey == "" or backdropTextureKey == "DEFAULT"
	if inheritedBackdropTexture then
		backdropTextureKey = options.textureKey or cfg.texture
		if UFHelper and UFHelper.shouldUseSolidBackdropForTexture and UFHelper.shouldUseSolidBackdropForTexture(backdropTextureKey) then backdropTextureKey = "SOLID" end
	end
	local backdropTexture = (UFHelper and UFHelper.resolveTexture and UFHelper.resolveTexture(backdropTextureKey)) or backdropTextureKey
	if not backdropTexture or backdropTexture == "" then backdropTexture = "Interface\\Buttons\\WHITE8x8" end

	if not enabled then
		if bar._eqolBackdropEnabled == false and bar._eqolBackdropClampToFill == clampToFill then return end
		if bar.SetBackdrop then bar:SetBackdrop(nil) end
		if bar._eqolBackdropTexture then bar._eqolBackdropTexture:Hide() end
		bar._eqolBackdropEnabled = false
		bar._eqolBackdropR, bar._eqolBackdropG, bar._eqolBackdropB, bar._eqolBackdropA = nil, nil, nil, nil
		bar._eqolBackdropConfigured = nil
		bar._eqolBackdropClampToFill = clampToFill
		bar._eqolBackdropStatusTex = nil
		bar._eqolBackdropTexturePath = nil
		return
	end
	if
		bar._eqolBackdropEnabled == true
		and bar._eqolBackdropConfigured == true
		and bar._eqolBackdropR == r
		and bar._eqolBackdropG == g
		and bar._eqolBackdropB == b
		and bar._eqolBackdropA == a
		and bar._eqolBackdropClampToFill == clampToFill
		and bar._eqolBackdropStatusTex == currentStatusTex
		and bar._eqolBackdropTexturePath == backdropTexture
	then
		return
	end

	if clampToFill then
		if bar.SetBackdrop then bar:SetBackdrop(nil) end
		local tex = bar._eqolBackdropTexture
		if not tex then
			tex = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(bar, nil, "BACKGROUND")) or bar:CreateTexture(nil, "BACKGROUND")
			bar._eqolBackdropTexture = tex
		end
		local htex = bar.GetStatusBarTexture and bar:GetStatusBarTexture()
		tex:ClearAllPoints()
		if htex then
			tex:SetPoint("TOPLEFT", htex, "TOPRIGHT", 0, 0)
			tex:SetPoint("BOTTOMLEFT", htex, "BOTTOMRIGHT", 0, 0)
			tex:SetPoint("TOPRIGHT", bar, "TOPRIGHT", 0, 0)
			tex:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", 0, 0)
		else
			tex:SetAllPoints(bar)
		end
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(tex, backdropTexture)
		else
			tex:SetTexture(backdropTexture)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(tex) end
		end
		if tex.SetHorizTile then tex:SetHorizTile(false) end
		if tex.SetVertTile then tex:SetVertTile(false) end
		if tex.SetVertexColor then tex:SetVertexColor(r, g, b, a) end
		tex:Show()
	else
		if not bar.SetBackdrop then return end
		if bar._eqolBackdropTexture then bar._eqolBackdropTexture:Hide() end
		bar:SetBackdrop({
			bgFile = backdropTexture,
			edgeFile = nil,
			tile = false,
		})
		bar:SetBackdropColor(r, g, b, a)
	end

	bar._eqolBackdropEnabled = true
	bar._eqolBackdropConfigured = true
	bar._eqolBackdropClampToFill = clampToFill
	bar._eqolBackdropStatusTex = currentStatusTex
	bar._eqolBackdropTexturePath = backdropTexture
	bar._eqolBackdropR, bar._eqolBackdropG, bar._eqolBackdropB, bar._eqolBackdropA = r, g, b, a
end

function GF._applyOverlayHeight(bar, anchor, height, maxHeight, anchorTop)
	if not bar or not anchor then return end
	bar:ClearAllPoints()
	local desired = tonumber(height)
	if not desired or desired <= 0 then
		bar:SetAllPoints(anchor)
		return
	end
	local limit = tonumber(maxHeight)
	if not limit or limit <= 0 then limit = anchor.GetHeight and anchor:GetHeight() or 0 end
	if limit and limit > 0 and desired > limit then desired = limit end
	if anchorTop then
		bar:SetPoint("TOPLEFT", anchor, "TOPLEFT", 0, 0)
		bar:SetPoint("TOPRIGHT", anchor, "TOPRIGHT", 0, 0)
	else
		bar:SetPoint("BOTTOMLEFT", anchor, "BOTTOMLEFT", 0, 0)
		bar:SetPoint("BOTTOMRIGHT", anchor, "BOTTOMRIGHT", 0, 0)
	end
	if Pixel and Pixel.SetHeight then
		Pixel.SetHeight(bar, desired)
	else
		bar:SetHeight(desired)
	end
end

function GF._ensureOverlayClipFrame(anchor, key)
	if not anchor then return nil end
	key = key or "_eqolOverlayClip"
	local clip = anchor[key]
	if not clip then
		clip = CreateFrame("Frame", nil, anchor)
		clip:SetClipsChildren(true)
		anchor[key] = clip
	end
	clip:ClearAllPoints()
	clip:SetAllPoints(anchor)
	if clip.SetFrameStrata and anchor.GetFrameStrata then
		local anchorStrata = anchor:GetFrameStrata()
		if anchorStrata and clip:GetFrameStrata() ~= anchorStrata then clip:SetFrameStrata(anchorStrata) end
	end
	if clip.SetFrameLevel and anchor.GetFrameLevel then
		local desiredLevel = (anchor:GetFrameLevel() or 0) + 1
		if clip:GetFrameLevel() ~= desiredLevel then clip:SetFrameLevel(desiredLevel) end
	end
	return clip
end

function GF._computeOverlayHeightFallback(frameHeight, powerHeight)
	local totalHeight = clampNumber(tonumber(frameHeight) or 24, 10, 200, 24)
	local power = clampNumber(tonumber(powerHeight) or 6, 0, 50, 6)
	if power > totalHeight - 4 then power = max(0, totalHeight * 0.25) end
	if power > totalHeight then power = totalHeight end
	local fallback = totalHeight - power
	if fallback < 1 then fallback = 1 end
	if fallback > 300 then fallback = 300 end
	return fallback
end

function GF._resolveOverlayHeightSetting(value, fallback)
	local v = tonumber(value)
	if not v then return fallback end
	if v <= 0 then return 0 end
	if v >= fallback then return 0 end
	return v
end

function GF._resolveRuntimeOverlayHeightSetting(value, configuredFallback, runtimeHeight)
	local runtimeLimit = tonumber(runtimeHeight)
	if not runtimeLimit or runtimeLimit <= 0 then runtimeLimit = tonumber(configuredFallback) or 1 end
	local baseFallback = tonumber(configuredFallback)
	if not baseFallback or baseFallback <= 0 then baseFallback = runtimeLimit end
	local v = tonumber(value)
	if not v or v <= 0 then return runtimeLimit end
	if v >= baseFallback then return runtimeLimit end
	if v > runtimeLimit then return runtimeLimit end
	return v
end

local function getEffectiveBarTexture(cfg, barCfg)
	local tex = cfg and cfg.barTexture
	if tex == nil or tex == "" or tex == BAR_TEX_INHERIT then tex = barCfg and barCfg.texture end
	return tex
end

local function usesPerBarTextureSelection(cfg)
	local tex = cfg and cfg.barTexture
	return tex == nil or tex == "" or tex == BAR_TEX_INHERIT
end

function GF.ResolveGroupPortraitConfig(cfg, kind)
	if kind == "raid" then return false, "LEFT", false, false, "PORTRAIT" end
	local def = (kind and DEFAULTS[kind]) or DEFAULTS.party or {}
	local pdef = def and def.portrait or {}
	local pcfg = (cfg and cfg.portrait) or {}
	local enabled = pcfg.enabled
	if enabled == nil then enabled = pdef.enabled end
	local side = tostring(pcfg.side or pdef.side or "LEFT"):upper()
	if side ~= "RIGHT" then side = "LEFT" end
	local squareBackground = pcfg.squareBackground
	if squareBackground == nil then squareBackground = pdef.squareBackground end
	local borderWithFrame = pcfg.borderWithFrame
	if borderWithFrame == nil then borderWithFrame = pdef.borderWithFrame end
	local mode = tostring(pcfg.mode or pdef.mode or "PORTRAIT"):upper()
	if mode ~= "CLASS_ICON" then mode = "PORTRAIT" end
	return enabled == true, side, squareBackground == true, borderWithFrame == true, mode
end

function GF.IsGroupPowerDetached(cfg, kind)
	if kind ~= "party" then return false end
	local def = (kind and DEFAULTS[kind]) or DEFAULTS.party or {}
	local pdef = def and def.power or {}
	local pcfg = (cfg and cfg.power) or {}
	local detached = pcfg.detached
	if detached == nil then detached = pdef.detached end
	return detached == true
end

function GF.ResolveGroupPortraitDetachedConfig(cfg, kind)
	if kind ~= "party" then return false, 0, 0, nil end
	local def = (kind and DEFAULTS[kind]) or DEFAULTS.party or {}
	local pdef = def and def.portrait or {}
	local pcfg = (cfg and cfg.portrait) or {}
	local detached = pcfg.detached
	if detached == nil then detached = pdef.detached end
	local offset = pcfg.detachedOffset or EMPTY
	local size = tonumber(pcfg.detachedSize or pdef.detachedSize)
	return detached == true, tonumber(offset.x), tonumber(offset.y), size
end

function GF.GetDefaultDetachedPortraitOffset(cfg, kind, frameWidth, frameHeight, portraitSize)
	local _, growth = GF.ResolveUnitGrowthDirection((cfg and cfg.growth) or (DEFAULTS[kind] and DEFAULTS[kind].growth), "DOWN")
	local fw = tonumber(frameWidth) or tonumber(cfg and cfg.width) or tonumber(DEFAULTS[kind] and DEFAULTS[kind].width) or 0
	local fh = tonumber(frameHeight) or tonumber(cfg and cfg.height) or tonumber(DEFAULTS[kind] and DEFAULTS[kind].height) or 0
	local size = tonumber(portraitSize) or tonumber(cfg and cfg.portrait and cfg.portrait.detachedSize) or fh
	if growth == "RIGHT" or growth == "LEFT" then return 0, (fh * 0.5) + (size * 0.5) end
	return -((fw * 0.5) + (size * 0.5)), 0
end

function GF.ResolveGroupPortraitSeparatorConfig(cfg, kind, portraitEnabled)
	if not portraitEnabled then return false, 0, "SOLID", nil end
	local def = (kind and DEFAULTS[kind]) or DEFAULTS.party or {}
	local pdef = def and def.portrait or {}
	local sdef = pdef.separator or {}
	local pcfg = (cfg and cfg.portrait) or {}
	local scfg = pcfg.separator or {}

	local separatorEnabled = scfg.enabled
	if separatorEnabled == nil then separatorEnabled = sdef.enabled end
	if separatorEnabled == nil then separatorEnabled = true end

	local separatorSize = tonumber(scfg.size)
	if not separatorSize then separatorSize = tonumber(sdef.size) end
	if not separatorSize then
		local borderCfg = (cfg and cfg.border) or {}
		local borderDef = (def and def.border) or {}
		separatorSize = tonumber(borderCfg.edgeSize or borderDef.edgeSize) or 1
	end
	if separatorSize < 1 then separatorSize = 1 end

	local separatorTexture = scfg.texture or sdef.texture or "SOLID"
	local separatorColor
	local useCustomColor = scfg.useCustomColor
	if useCustomColor == nil then useCustomColor = sdef.useCustomColor end
	if useCustomColor == true then separatorColor = scfg.color or sdef.color end
	if type(separatorColor) ~= "table" then
		local borderCfg = (cfg and cfg.border) or {}
		local borderDef = (def and def.border) or {}
		separatorColor = borderCfg.color or borderDef.color or { 0, 0, 0, 0.8 }
	end

	return separatorEnabled == true, separatorSize, separatorTexture, separatorColor
end

function GF.ApplyGroupPortraitSeparator(cfg, kind, st, portraitEnabled)
	if not st or not st.portraitSeparator or not st.portraitHolder then return end
	if not portraitEnabled then
		st.portraitSeparator:Hide()
		return
	end

	local separatorEnabled, separatorSize, separatorTexture, separatorColor = GF.ResolveGroupPortraitSeparatorConfig(cfg, kind, portraitEnabled)
	if not separatorEnabled or separatorSize <= 0 then
		st.portraitSeparator:Hide()
		return
	end

	local color = separatorColor or { 0, 0, 0, 0.8 }
	if Pixel and Pixel.SetTexture then
		Pixel.SetTexture(st.portraitSeparator, UFHelper.resolveSeparatorTexture(separatorTexture))
	else
		st.portraitSeparator:SetTexture(UFHelper.resolveSeparatorTexture(separatorTexture))
		if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.portraitSeparator) end
	end
	st.portraitSeparator:SetVertexColor(color[1] or 0, color[2] or 0, color[3] or 0, color[4] or 1)
	st.portraitSeparator:ClearAllPoints()
	local side = st._portraitSide or "LEFT"
	if side == "RIGHT" then
		st.portraitSeparator:SetPoint("TOP", st.portraitHolder, "TOP", 0, 0)
		st.portraitSeparator:SetPoint("BOTTOM", st.portraitHolder, "BOTTOM", 0, 0)
		st.portraitSeparator:SetPoint("RIGHT", st.portraitHolder, "LEFT", 0, 0)
	else
		st.portraitSeparator:SetPoint("TOP", st.portraitHolder, "TOP", 0, 0)
		st.portraitSeparator:SetPoint("BOTTOM", st.portraitHolder, "BOTTOM", 0, 0)
		st.portraitSeparator:SetPoint("LEFT", st.portraitHolder, "RIGHT", 0, 0)
	end
	if Pixel and Pixel.SetWidth then
		Pixel.SetWidth(st.portraitSeparator, separatorSize, 1)
	else
		st.portraitSeparator:SetWidth(separatorSize)
	end
	st.portraitSeparator:Show()
end

local function stabilizeStatusBarTexture(bar)
	if not bar then return end
	if GFH.Pixel and GFH.Pixel.DisableSnap then GFH.Pixel.DisableSnap(bar) end
	if not bar.GetStatusBarTexture then return end
	local t = bar:GetStatusBarTexture()
	if not t then return end
	if t.SetHorizTile then t:SetHorizTile(false) end
	if t.SetVertTile then t:SetVertTile(false) end
	if t.SetTexCoord then t:SetTexCoord(0, 1, 0, 1) end
	if GFH.Pixel and GFH.Pixel.DisableSnap then GFH.Pixel.DisableSnap(t) end
end

local roundToPixel = GFH.RoundToPixel

local function roundToEvenPixel(value, scale)
	value = tonumber(value) or 0
	if value == 0 then return 0 end
	-- Snap half-size to pixel grid and double it so final size always lands on an even pixel count.
	if Pixel and Pixel.RoundEven then return Pixel.RoundEven(value, scale, 2) end
	return roundToPixel(value * 0.5, scale) * 2
end

function GF.NormalizeRaidAutoFitScale(value)
	value = tonumber(value) or 1
	if value <= 0 or value > 1 then return 1 end
	return value
end

function GF.GetRaidAutoFitPixelStep(scale)
	scale = tonumber(scale) or 1
	if scale <= 0 then scale = 1 end
	local factor = (Pixel and Pixel.GetPixelToUIUnitFactor and Pixel.GetPixelToUIUnitFactor()) or 1
	return factor / scale
end

function GF.FloorRaidAutoFitPixel(value, scale, minPixels)
	value = tonumber(value) or 0
	if value == 0 and (not minPixels or minPixels == 0) then return 0 end
	local sign = value < 0 and -1 or 1
	local step = GF.GetRaidAutoFitPixelStep(scale)
	local pixels = floor((math.abs(value) / step) + 0.000001)
	minPixels = tonumber(minPixels) or 0
	if pixels < minPixels then pixels = minPixels end
	return sign * pixels * step
end

function GF.FloorEvenRaidAutoFitPixel(value, scale, minEvenPixels)
	value = tonumber(value) or 0
	if value == 0 and (not minEvenPixels or minEvenPixels == 0) then return 0 end
	local sign = value < 0 and -1 or 1
	local step = GF.GetRaidAutoFitPixelStep(scale)
	local pixels = floor((math.abs(value) / step) + 0.000001)
	pixels = floor(pixels / 2) * 2
	minEvenPixels = tonumber(minEvenPixels) or 2
	if pixels < minEvenPixels then pixels = minEvenPixels end
	return sign * pixels * step
end

function GF.GetRaidAutoFitLineExtent(unitSize, spacingValue, count)
	unitSize = tonumber(unitSize) or 0
	spacingValue = tonumber(spacingValue) or 0
	count = max(1, floor((tonumber(count) or 1) + 0.5))
	return unitSize * count + spacingValue * max(0, count - 1)
end

function GF.GetRaidAutoFitCrossMetrics(unitSize, spacingValue, viewportCount, runtimeCount, fitScale, scale, evenSize)
	fitScale = GF.NormalizeRaidAutoFitScale(fitScale)
	unitSize = tonumber(unitSize) or 0
	spacingValue = tonumber(spacingValue) or 0
	viewportCount = max(1, floor((tonumber(viewportCount) or 1) + 0.5))
	runtimeCount = max(1, floor((tonumber(runtimeCount) or 1) + 0.5))

	if fitScale >= 1 or runtimeCount <= viewportCount then
		local fittedSize = evenSize and roundToEvenPixel(unitSize, scale) or roundToPixel(unitSize, scale)
		return fittedSize, roundToPixel(spacingValue, scale), 1
	end

	local fittedSize = evenSize and roundToEvenPixel(max(1, unitSize * fitScale), scale) or roundToPixel(max(1, unitSize * fitScale), scale)
	local fittedSpacing = roundToPixel(max(0, spacingValue * fitScale), scale)
	local viewportExtent = GF.GetRaidAutoFitLineExtent(unitSize, spacingValue, viewportCount)
	if GF.GetRaidAutoFitLineExtent(fittedSize, fittedSpacing, runtimeCount) > viewportExtent + 0.01 then
		local availableForUnits = max(1, viewportExtent - fittedSpacing * max(0, runtimeCount - 1))
		fittedSize = availableForUnits / runtimeCount
		if evenSize then
			fittedSize = GF.FloorEvenRaidAutoFitPixel(fittedSize, scale, 2)
		else
			fittedSize = GF.FloorRaidAutoFitPixel(fittedSize, scale, 1)
		end
		if GF.GetRaidAutoFitLineExtent(fittedSize, fittedSpacing, runtimeCount) > viewportExtent + 0.01 then
			local gaps = max(0, runtimeCount - 1)
			if gaps > 0 then
				local availableForSpacing = max(0, (viewportExtent - fittedSize * runtimeCount) / gaps)
				fittedSpacing = GF.FloorRaidAutoFitPixel(availableForSpacing, scale, 0)
			end
		end
	end

	return fittedSize, fittedSpacing, fitScale
end

function GF.GetRaidAutoFitLayoutMetrics(growth, width, height, xOffset, yOffset, columnSpacing, fitScale, scale, viewportCount, runtimeCount)
	fitScale = GF.NormalizeRaidAutoFitScale(fitScale)
	local renderW = width
	local renderH = height
	local renderXOffset = xOffset
	local renderYOffset = yOffset
	local renderColumnSpacing = columnSpacing

	if fitScale < 1 then
		if growth == "RIGHT" or growth == "LEFT" then
			renderH, renderColumnSpacing = GF.GetRaidAutoFitCrossMetrics(height, columnSpacing, viewportCount, runtimeCount, fitScale, scale, true)
		else
			renderW, renderColumnSpacing = GF.GetRaidAutoFitCrossMetrics(width, columnSpacing, viewportCount, runtimeCount, fitScale, scale, true)
		end
	end

	return renderW, renderH, renderXOffset, renderYOffset, renderColumnSpacing, fitScale
end

function GF.SetRegionWidth(region, width, scale)
	if not region then return end
	width = max(1, tonumber(width) or 1)
	if Pixel and Pixel.SetWidth then
		Pixel.SetWidth(region, width, 1)
	else
		region:SetWidth(roundToPixel(width, scale))
	end
end

function GF.SetRegionHeight(region, height, scale)
	if not region then return end
	height = max(1, tonumber(height) or 1)
	if Pixel and Pixel.SetHeight then
		Pixel.SetHeight(region, height, 1)
	else
		region:SetHeight(roundToPixel(height, scale))
	end
end

function GF.ShouldForceLiveGroupButtonSize(kind) return kind == "party" or kind == "mt" or kind == "ma" end

function GF.ForceLiveGroupButtonSize(self, kind, cfg, width, height)
	if not (self and self.SetSize) then return false end
	if InCombatLockdown and InCombatLockdown() then return false end

	kind = kind or self._eqolGroupKind or "party"
	if not GF.ShouldForceLiveGroupButtonSize(kind) then return false end

	cfg = cfg or self._eqolCfg or getCfg(kind)
	local scale = GFH.GetEffectiveScale(UIParent)
	local def = DEFAULTS[kind] or DEFAULTS.party or {}
	local w = tonumber(width)
	local h = tonumber(height)

	if not w then w = clampNumber(tonumber(cfg and cfg.width) or tonumber(def.width) or 100, 40, 600, 100) end
	if not h then h = clampNumber(tonumber(cfg and cfg.height) or tonumber(def.height) or 24, 10, 200, 24) end
	if w <= 0 or h <= 0 then return false end

	w = roundToEvenPixel(w, scale)
	h = roundToEvenPixel(h, scale)
	if Pixel and Pixel.SetSize then
		Pixel.SetSize(self, w, h, 1, 1)
	else
		self:SetSize(w, h)
	end
	return true
end

GF.GetLayoutAnchorFrame = GF.GetLayoutAnchorFrame or function(st, fallback)
	if st and st.layoutAnchor then return st.layoutAnchor end
	if st and st.frame then return st.frame end
	return fallback
end

local layoutTexts = GFH.LayoutTexts

function GF.ApplyDataBarNameCharLimit(self, st, cfg, def, rootCfg)
	if not st then return end
	cfg = cfg or EMPTY
	def = def or EMPTY
	local maxChars = tonumber(cfg.nameMaxChars)
	if maxChars == nil then maxChars = tonumber(def.nameMaxChars) end
	maxChars = maxChars or 0
	local width
	if maxChars > 0 and UFHelper and UFHelper.getNameLimitWidth then
		local fontSize = GF.ScaleContentValue(self, cfg.fontSize or def.fontSize or 12, rootCfg, 1)
		width = UFHelper.getNameLimitWidth(cfg.font or def.font, fontSize, cfg.fontOutline or def.fontOutline or "OUTLINE", maxChars)
	end
	local function apply(fontString, mode)
		if not fontString then return end
		if fontString.SetMaxLines then fontString:SetMaxLines(1) end
		if fontString.SetWordWrap then fontString:SetWordWrap(false) end
		if fontString.SetNonSpaceWrap then fontString:SetNonSpaceWrap(false) end
		if tostring(mode or "NONE"):upper() == "NAME" and width and width > 0 then
			fontString:SetWidth(width)
		else
			fontString:SetWidth(0)
		end
	end
	apply(st.dataBarTextLeft, cfg.textLeft or def.textLeft or "NONE")
	apply(st.dataBarTextCenter, cfg.textCenter or def.textCenter or "NONE")
	apply(st.dataBarTextRight, cfg.textRight or def.textRight or "NONE")
end

local function setFrameLevelAbove(child, parent, offset)
	if not child or not parent then return end
	if child.SetFrameStrata and parent.GetFrameStrata then child:SetFrameStrata(parent:GetFrameStrata()) end
	if child.SetFrameLevel and parent.GetFrameLevel then child:SetFrameLevel(GF.ClampFrameLevel((parent:GetFrameLevel() or 0) + (offset or 1))) end
end

function GF.SyncDispelTintLayer(st)
	if not (st and st.dispelTint) then return end
	local anchor = st.barGroup or st.health or st.frame
	if not anchor then return end
	local cfg = st.frame and st.frame._eqolCfg or nil
	local dispelCfg = cfg and cfg.status and cfg.status.dispelTint or EMPTY
	local parent = st.statusIconLayer or st.layoutAnchor or anchor
	if st.dispelTint.GetParent and st.dispelTint:GetParent() ~= parent then st.dispelTint:SetParent(parent) end
	st.dispelTint:ClearAllPoints()
	st.dispelTint:SetAllPoints(anchor)
	local levelOffset = tonumber(dispelCfg.frameLevelOffset)
	if levelOffset == nil then levelOffset = 1 end
	GF.SyncFrameLayerAbove(st.dispelTint, st.statusIconLayer or st.healthTextLayer or st.powerTextLayer or anchor, levelOffset, dispelCfg.strata)
end

local function syncTextFrameLevels(st)
	if not st then return end
	local frame = st.frame
	local cfg = frame and frame._eqolCfg or nil
	local scfg = cfg and cfg.status or EMPTY
	GF.SyncFrameLayerAbove(st.healthTextLayer, st.health, 5)
	GF.SyncFrameLayerAbove(st.powerTextLayer, st.power, 5)
	local nameLevelOffset = tonumber(scfg.nameFrameLevelOffset)
	if nameLevelOffset == nil then nameLevelOffset = 5 end
	GF.SyncFrameLayerAbove(st.nameTextLayer or st.healthTextLayer, st.health, nameLevelOffset, scfg.nameStrata)
	local levelOffset = tonumber(scfg.levelFrameLevelOffset)
	if levelOffset == nil then levelOffset = 5 end
	GF.SyncFrameLayerAbove(st.levelTextLayer or st.healthTextLayer, st.health, levelOffset, scfg.levelStrata)
	if st.statusIconLayer then
		local parent = st.healthTextLayer or st.health or GF.GetLayoutAnchorFrame(st, st.barGroup) or st.barGroup or st.frame
		local anchor = GF.GetLayoutAnchorFrame(st, st.barGroup)
		GF.SyncFrameLayerAbove(st.statusIconLayer, parent, 6)
		if anchor and st.statusIconLayer.SetAllPoints then st.statusIconLayer:SetAllPoints(anchor) end
	end
	GF.SyncDispelTintLayer(st)
end

local function hookTextFrameLevels(st)
	if not st or not hooksecurefunc then return end
	st._textLevelHooks = st._textLevelHooks or {}
	local function hookFrame(frame)
		if not frame or st._textLevelHooks[frame] then return end
		st._textLevelHooks[frame] = true
		if frame.SetFrameLevel then hooksecurefunc(frame, "SetFrameLevel", function() syncTextFrameLevels(st) end) end
		if frame.SetFrameStrata then hooksecurefunc(frame, "SetFrameStrata", function() syncTextFrameLevels(st) end) end
	end
	hookFrame(st.frame)
	hookFrame(st.layoutAnchor)
	hookFrame(st.barGroup)
	hookFrame(st.health)
	hookFrame(st.power)
	syncTextFrameLevels(st)
end

local function getClassColor(class)
	if not class then return nil end
	if addon.db and addon.db.ufUseCustomClassColors then
		local overrides = addon.db.ufClassColors
		local custom = overrides and overrides[class]
		if custom then
			if custom.r then return custom.r, custom.g, custom.b, custom.a or 1 end
			if custom[1] then return custom[1], custom[2], custom[3], custom[4] or 1 end
		end
	end
	local fallback = (CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS[class]) or (RAID_CLASS_COLORS and RAID_CLASS_COLORS[class])
	if fallback then return fallback.r or fallback[1], fallback.g or fallback[2], fallback.b or fallback[3], fallback.a or fallback[4] or 1 end
	return nil
end

local function getUnitClassToken(unit)
	if not unit then return nil end
	local _, class = UnitClass(unit)
	if issecretvalue and issecretvalue(class) then class = nil end
	if class and class ~= "" then return tostring(class) end
	if UnitInRaid and GetRaidRosterInfo then
		local raidIndex = UnitInRaid(unit)
		if raidIndex and not (issecretvalue and issecretvalue(raidIndex)) then
			local _, _, _, _, _, classFile = GetRaidRosterInfo(raidIndex)
			if not (issecretvalue and issecretvalue(classFile)) and classFile and classFile ~= "" then return tostring(classFile) end
		end
	end
	if UnitGUID and GetPlayerInfoByGUID then
		local guid = UnitGUID(unit)
		if not (issecretvalue and issecretvalue(guid)) and guid and guid ~= "" then
			local _, classFile = GetPlayerInfoByGUID(guid)
			if not (issecretvalue and issecretvalue(classFile)) and classFile and classFile ~= "" then return tostring(classFile) end
		end
	end
	return nil
end

local function unpackColor(color, fallback)
	if not color then color = fallback end
	if not color then return 1, 1, 1, 1 end
	if color.r then return color.r, color.g, color.b, color.a or 1 end
	return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1
end

function GF.GetUnitThreatStatus(unit)
	if not (unit and UnitThreatSituation and UnitExists and UnitExists(unit)) then return nil end
	local status = UnitThreatSituation(unit)
	-- Threat state can be secret-restricted in Retail; hide the indicator instead of surfacing stale state.
	if issecretvalue and issecretvalue(status) then return nil end
	status = tonumber(status)
	if not status or status <= 0 then return nil end
	status = floor(status + 0.5)
	if status < 1 then return nil end
	if status > 3 then status = 3 end
	return status
end

function GF.GetThreatHighlightColor(status, fallbackColor) return unpackColor(fallbackColor, { 1, 0.55, 0, 1 }) end

function GF.NormalizeAggroHighlightMode(value)
	local mode = tostring(value or "ALL"):upper()
	if mode == "NON_TANK" or mode == "NON_TANKS" then return "NON_TANKS" end
	return "ALL"
end

local function formatGroupNumber(subgroup, format)
	local num = tonumber(subgroup)
	if not num then return nil end
	local fmt = normalizeGroupNumberFormat(format) or "GROUP"
	if fmt == "NUMBER" then return tostring(num) end
	if fmt == "PARENS" then return "(" .. num .. ")" end
	if fmt == "BRACKETS" then return "[" .. num .. "]" end
	if fmt == "BRACES" then return "{" .. num .. "}" end
	if fmt == "PIPE" then return "|| " .. num .. " ||" end
	if fmt == "ANGLE" then return "<" .. num .. ">" end
	if fmt == "G" then return "G" .. num end
	if fmt == "G_SPACE" then return "G " .. num end
	if fmt == "HASH" then return "#" .. num end
	return string.format(GROUP_NUMBER or "Group %d", num)
end

local function getGroupNumberConfig(cfg, def)
	local sc = cfg and cfg.status or {}
	local us = sc.unitStatus or {}
	local gn = sc.groupNumber or {}
	local defStatus = def and def.status or {}
	local defUS = defStatus.unitStatus or {}
	local defGN = defStatus.groupNumber or {}
	return sc, us, gn, defUS, defGN
end

local function resolveGroupByValue(cfg, def)
	local groupBy = (cfg and cfg.groupBy) or (def and def.groupBy)
	local v = tostring(groupBy or "GROUP"):upper()
	if v == "ROLE" then v = "ASSIGNEDROLE" end
	if v == "CLASS" then v = "GROUP" end
	if v == "GROUP" or v == "ASSIGNEDROLE" then return v end
	return nil
end

local function resolveSortMethod(cfg)
	local raw = cfg and cfg.sortMethod
	local v = tostring(raw or ""):upper()
	if v == "CUSTOM" then v = "NAMELIST" end
	if GFH and GFH.NormalizeSortMethod then
		v = GFH.NormalizeSortMethod(v)
	elseif v ~= "NAME" and v ~= "INDEX" and v ~= "NAMELIST" then
		v = "INDEX"
	end
	local custom = cfg and GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
	if v ~= "NAMELIST" and custom and custom.enabled == true then
		if raw == nil or raw == "" or tostring(raw):upper() == "CUSTOM" then v = "NAMELIST" end
	end
	return v
end

function GF.NormalizeUnitGrowthMode(value, fallback)
	local fb = tostring(fallback or "DOWN"):upper()
	local v = tostring(value or ""):upper()
	if v == "CENTER" or v == "CENTERH" or v == "CENTER_HORIZONTAL" or v == "CENTER-HORIZONTAL" or v == "CENTERHORIZONTAL" then return "CENTER_HORIZONTAL" end
	if v == "CENTERV" or v == "CENTER_VERTICAL" or v == "CENTER-VERTICAL" or v == "CENTERVERTICAL" then return "CENTER_VERTICAL" end
	if GFH and GFH.NormalizeGrowthDirection then
		local normalized = GFH.NormalizeGrowthDirection(v, nil)
		if normalized then return normalized end
	else
		if v == "UP" or v == "DOWN" or v == "LEFT" or v == "RIGHT" then return v end
	end
	if fb == "CENTER_HORIZONTAL" or fb == "CENTER_VERTICAL" then return fb end
	if GFH and GFH.NormalizeGrowthDirection then return GFH.NormalizeGrowthDirection(fb, "DOWN") end
	if fb == "UP" or fb == "DOWN" or fb == "LEFT" or fb == "RIGHT" then return fb end
	return "DOWN"
end

function GF.ResolveUnitGrowthDirection(value, fallback)
	local mode = GF.NormalizeUnitGrowthMode(value, fallback)
	if mode == "CENTER_HORIZONTAL" then return mode, "RIGHT" end
	if mode == "CENTER_VERTICAL" then return mode, "DOWN" end
	return mode, mode
end

function GF.ResolveRaidCrossGrowthValue(groupGrowth, unitGrowth)
	local unit = (GFH and GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(unitGrowth, "DOWN")) or tostring(unitGrowth or "DOWN"):upper()
	if GFH and GFH.ResolveGroupGrowthDirection then return GFH.ResolveGroupGrowthDirection(groupGrowth, unit, nil) end
	return (GFH and GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(groupGrowth, nil)) or ((unit == "RIGHT" or unit == "LEFT") and "DOWN" or "RIGHT")
end

function GF.ResolveRaidCrossGrowth(cfg, growth)
	local _, unitGrowth = GF.ResolveUnitGrowthDirection(growth or (cfg and cfg.growth), "DOWN")
	return GF.ResolveRaidCrossGrowthValue(cfg and cfg.groupGrowth, unitGrowth)
end

function GF.GetRaidLayoutStartPoint(growth, groupGrowth)
	local _, unitGrowth = GF.ResolveUnitGrowthDirection(growth, "DOWN")
	local crossGrowth = GF.ResolveRaidCrossGrowthValue(groupGrowth, unitGrowth)
	if unitGrowth == "RIGHT" or unitGrowth == "LEFT" then
		local horizontalPoint = (unitGrowth == "LEFT") and "RIGHT" or "LEFT"
		local verticalPoint = (crossGrowth == "UP") and "BOTTOM" or "TOP"
		return verticalPoint .. horizontalPoint
	end
	local horizontalPoint = (crossGrowth == "LEFT") and "RIGHT" or "LEFT"
	local verticalPoint = (unitGrowth == "UP") and "BOTTOM" or "TOP"
	return verticalPoint .. horizontalPoint
end

function GF.GetRaidColumnAnchorPoint(growth, groupGrowth)
	local _, unitGrowth = GF.ResolveUnitGrowthDirection(growth, "DOWN")
	local crossGrowth = GF.ResolveRaidCrossGrowthValue(groupGrowth, unitGrowth)
	if unitGrowth == "RIGHT" or unitGrowth == "LEFT" then return (crossGrowth == "UP") and "BOTTOM" or "TOP" end
	return (crossGrowth == "LEFT") and "RIGHT" or "LEFT"
end

function GF.SupportsCenterGrowth(kind) return kind == "party" or kind == "raid" end

function GF.GetCenterGrowthRelativePoint(growth)
	local _, baseGrowth = GF.ResolveUnitGrowthDirection(growth, "DOWN")
	if baseGrowth == "RIGHT" or baseGrowth == "LEFT" then return "TOP" end
	return "LEFT"
end

function GF.GetPartyCenterGrowthRelativePoint(growth) return GF.GetCenterGrowthRelativePoint(growth) end

function GF.IsCenterGrowthMode(kind, cfg)
	if not (cfg and GF.SupportsCenterGrowth(kind)) then return false end
	local mode = GF.NormalizeUnitGrowthMode(cfg.growth, "DOWN")
	return mode == "CENTER_HORIZONTAL" or mode == "CENTER_VERTICAL"
end

function GF.IsPartyCenterGrowthMode(cfg) return GF.IsCenterGrowthMode("party", cfg) end

function GF.BuildPartyCenterGrowthNameList(cfg)
	local sortMethod = resolveSortMethod(cfg)
	local nameList

	if sortMethod == "NAMELIST" and GFH and GFH.BuildCustomSortNameList then
		nameList = GFH.BuildCustomSortNameList(cfg, "party")
	else
		local names, seen = {}, {}
		local function addName(unit)
			if not (unit and UnitExists and UnitExists(unit)) then return end
			local name = GFH and GFH.GetUnitFullName and GFH.GetUnitFullName(unit)
			if not name and UnitName then
				local rawName, realm = UnitName(unit)
				if rawName and rawName ~= "" then
					if realm and realm ~= "" then
						name = rawName .. "-" .. realm
					else
						name = rawName
					end
				end
			end
			if not name or seen[name] then return end
			seen[name] = true
			names[#names + 1] = name
		end

		local showPlayer = cfg and cfg.showPlayer == true
		local showSolo = cfg and cfg.showSolo == true
		local arenaPartyActive = GFH and GFH.IsArenaPartyActive and GFH.IsArenaPartyActive()
		if IsInRaid and IsInRaid() and not arenaPartyActive then
			showPlayer = false
			showSolo = false
		end
		if IsInGroup and IsInGroup() then
			if showPlayer then addName("player") end
			for i = 1, 4 do
				addName("party" .. i)
			end
		elseif showSolo then
			addName("player")
		end

		if sortMethod == "NAME" then table.sort(names) end
		if #names > 0 then nameList = table.concat(names, ",") end
	end

	if type(nameList) ~= "string" or nameList == "" then return nil, 0, 0 end
	local count = 0
	for token in nameList:gmatch("[^,]+") do
		local normalized = tostring(token):gsub("^%s+", ""):gsub("%s+$", "")
		if normalized ~= "" then count = count + 1 end
	end
	if count == 0 then return nil, 0, 0 end
	return nameList, 0, count
end

function GF.CountCsvTokens(value)
	if type(value) ~= "string" or value == "" then return 0 end
	local count = 0
	for token in value:gmatch("[^,]+") do
		local normalized = tostring(token):gsub("^%s+", ""):gsub("%s+$", "")
		if normalized ~= "" then count = count + 1 end
	end
	return count
end

function GF.TrimCsvToken(token)
	local normalized = tostring(token or "")
	normalized = normalized:gsub("^%s+", "")
	normalized = normalized:gsub("%s+$", "")
	return normalized
end

function GF.NormalizeRaidGroupsPerRow(value, fallback)
	local v = tonumber(value)
	if not v then v = tonumber(fallback) or 8 end
	v = floor(v + 0.5)
	if v < 2 then return 2 end
	if v > 8 then return 8 end
	return v
end

function GF.GetSelectedRaidGroups(cfg)
	local selected = {}
	local count = 0
	local hasNumericFilter = false

	if cfg and type(cfg.groupFilter) == "string" and cfg.groupFilter ~= "" then
		for token in cfg.groupFilter:gmatch("[^,]+") do
			local group = tonumber(GF.TrimCsvToken(token))
			if group and group >= 1 and group <= 8 and not selected[group] then
				selected[group] = true
				count = count + 1
				hasNumericFilter = true
			end
		end
	end

	if not hasNumericFilter then
		for group = 1, 8 do
			selected[group] = true
		end
		return selected, 8
	end

	return selected, count
end

function GF.IsRaidGroupShown(cfg, group)
	group = tonumber(group)
	if not group or group < 1 or group > 8 then return false end
	local selected = GF.GetSelectedRaidGroups(cfg)
	return selected[group] == true
end

function GF.BuildRaidGroupFilter(selected, cfg)
	if type(selected) ~= "table" then return nil, 8 end

	local ordering = (cfg and cfg.groupingOrder) or (GFH and GFH.GROUP_ORDER) or "1,2,3,4,5,6,7,8"
	local orderedGroups, seen = {}, {}
	if type(ordering) == "string" and ordering ~= "" then
		for token in ordering:gmatch("[^,]+") do
			local group = tonumber(GF.TrimCsvToken(token))
			if group and group >= 1 and group <= 8 and not seen[group] then
				seen[group] = true
				orderedGroups[#orderedGroups + 1] = group
			end
		end
	end
	for group = 1, 8 do
		if not seen[group] then orderedGroups[#orderedGroups + 1] = group end
	end

	local values = {}
	for _, group in ipairs(orderedGroups) do
		if selected[group] then values[#values + 1] = tostring(group) end
	end

	if #values >= 8 then return nil, #values end
	if #values == 0 then return nil, 0 end
	return table.concat(values, ","), #values
end

function GF.SetRaidGroupShown(cfg, group, shown)
	group = tonumber(group)
	if type(cfg) ~= "table" or not group or group < 1 or group > 8 then return false end

	local selected, count = GF.GetSelectedRaidGroups(cfg)
	shown = shown == true

	if shown then
		if selected[group] then return true, cfg.groupFilter, count end
		selected[group] = true
		count = count + 1
	else
		if not selected[group] then return true, cfg.groupFilter, count end
		if count <= 1 then return false, cfg.groupFilter, count end
		selected[group] = nil
		count = count - 1
	end

	local filter, selectedCount = GF.BuildRaidGroupFilter(selected, cfg)
	cfg.groupFilter = filter
	return true, filter, selectedCount
end

function GF.SyncRaidGroupFilterEditModeValues(editModeId, cfg)
	if not (EditMode and EditMode.SetValue and editModeId) then return end
	EditMode:SetValue(editModeId, "groupFilter", cfg and cfg.groupFilter or nil, nil, true)
	local selected = GF.GetSelectedRaidGroups(cfg)
	for group = 1, 8 do
		EditMode:SetValue(editModeId, "raidGroup" .. group, selected[group] == true, nil, true)
	end
end

function GF.ComputeCenterGrowthOffsetFromSpan(growth, span, scale)
	local _, baseGrowth = GF.ResolveUnitGrowthDirection(growth, "DOWN")
	span = max(0, tonumber(span) or 0)
	if span <= 0 then return 0, 0 end
	if baseGrowth == "LEFT" then return roundToPixel(span * 0.5, scale), 0 end
	if baseGrowth == "RIGHT" then return roundToPixel(span * -0.5, scale), 0 end
	if baseGrowth == "UP" then return 0, roundToPixel(span * -0.5, scale) end
	return 0, roundToPixel(span * 0.5, scale)
end

function GF.ComputeViewportCenteringOffset(growth, viewportSpan, contentSpan, scale)
	local direction = (GFH and GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(growth, nil)) or tostring(growth or ""):upper()
	viewportSpan = max(0, tonumber(viewportSpan) or 0)
	contentSpan = max(0, tonumber(contentSpan) or 0)
	local freeSpan = viewportSpan - contentSpan
	if freeSpan <= 0 then return 0, 0 end
	if direction == "LEFT" then return roundToPixel(freeSpan * -0.5, scale), 0 end
	if direction == "RIGHT" then return roundToPixel(freeSpan * 0.5, scale), 0 end
	if direction == "UP" then return 0, roundToPixel(freeSpan * 0.5, scale) end
	if direction == "DOWN" then return 0, roundToPixel(freeSpan * -0.5, scale) end
	return 0, 0
end

function GF.ComputePartyCenterGrowthAnchorOffset(cfg, growth, scale, countOverride)
	if not cfg then return 0, 0, 0 end
	local _, baseGrowth = GF.ResolveUnitGrowthDirection(growth or cfg.growth, "DOWN")
	local count = tonumber(countOverride)
	if not count then
		local _, _, dynamicCount = GF.BuildPartyCenterGrowthNameList(cfg)
		count = dynamicCount or 0
	end
	count = max(0, floor(count + 0.5))
	scale = scale or (GFH and GFH.GetEffectiveScale and GFH.GetEffectiveScale(UIParent)) or 1
	if count <= 0 then return 0, 0, 0 end

	local spacing = roundToPixel(clampNumber(tonumber(cfg.spacing) or 0, 0, GROUP_FRAME_MAX_SPACING, 0), scale)
	local w = roundToEvenPixel(clampNumber(tonumber(cfg.width) or 100, 40, 600, 100), scale)
	local h = roundToEvenPixel(clampNumber(tonumber(cfg.height) or 24, 10, 200, 24), scale)

	if baseGrowth == "RIGHT" or baseGrowth == "LEFT" then
		local totalSpan = count * w + spacing * max(0, count - 1)
		local x = GF.ComputeCenterGrowthOffsetFromSpan(baseGrowth, totalSpan, scale)
		return x, 0, count
	end
	local totalSpan = count * h + spacing * max(0, count - 1)
	local _, y = GF.ComputeCenterGrowthOffsetFromSpan(baseGrowth, totalSpan, scale)
	return 0, y, count
end

local function isGroupByGroup(cfg, def) return resolveGroupByValue(cfg, def) == "GROUP" end

local EMPTY_NAMELIST_TOKEN = "__EQOL_EMPTY__"

function GF.GetSafeFallbackSortMethod(value)
	local method = tostring(value or "INDEX"):upper()
	if method == "CUSTOM" or method == "NAMELIST" or method == "" then method = "INDEX" end
	if method ~= "NAME" and method ~= "INDEX" then method = "INDEX" end
	return method
end

local function isGroupCustomLayout(cfg)
	if not cfg then return false end
	if resolveSortMethod(cfg) ~= "NAMELIST" then return false end
	local rawGroupBy = cfg.groupBy
	local normalized = resolveGroupByValue(cfg, DEFAULTS.raid) or "GROUP"
	if normalized ~= "GROUP" then return false end
	if rawGroupBy and tostring(rawGroupBy):upper() == "CLASS" then return false end
	return true
end

function GF:IsRaidGroupedLayout(cfg)
	if not cfg then return false end
	local rawGroupBy = cfg.groupBy
	local normalized = resolveGroupByValue(cfg, DEFAULTS.raid) or "GROUP"
	if normalized ~= "GROUP" then return false end
	if rawGroupBy and tostring(rawGroupBy):upper() == "CLASS" then return false end
	return true
end

function GF:BuildDenseCustomGroupSpecs(cfg)
	if not (GFH and GFH.BuildCustomSortNameListsByGroup) then return {} end
	local sparseLists = GFH.BuildCustomSortNameListsByGroup(cfg) or {}

	local function trimCsvToken(token)
		local normalized = tostring(token or "")
		normalized = normalized:gsub("^%s+", "")
		normalized = normalized:gsub("%s+$", "")
		return normalized
	end

	local function buildOrderedGroups(onlyExisting)
		local ordering = (cfg and cfg.groupingOrder) or (GFH and GFH.GROUP_ORDER) or "1,2,3,4,5,6,7,8"
		local groups, seen = {}, {}
		if type(ordering) == "string" and ordering ~= "" then
			for token in ordering:gmatch("[^,]+") do
				local num = tonumber(trimCsvToken(token))
				if num and num >= 1 and num <= 8 and not seen[num] then
					seen[num] = true
					groups[#groups + 1] = num
				end
			end
		end
		if #groups == 0 then
			for i = 1, 8 do
				groups[#groups + 1] = i
				seen[i] = true
			end
		else
			for i = 1, 8 do
				if not seen[i] then groups[#groups + 1] = i end
			end
		end

		local numericFilter, hasNumericFilter = {}, false
		if cfg and type(cfg.groupFilter) == "string" and cfg.groupFilter ~= "" then
			for token in cfg.groupFilter:gmatch("[^,]+") do
				local num = tonumber(trimCsvToken(token))
				if num and num >= 1 and num <= 8 then
					numericFilter[num] = true
					hasNumericFilter = true
				end
			end
		end

		local existing
		if onlyExisting then
			existing = {}
			if IsInRaid and IsInRaid() and GetNumGroupMembers and GetRaidRosterInfo then
				for i = 1, GetNumGroupMembers() do
					local _, _, subgroup = GetRaidRosterInfo(i)
					subgroup = tonumber(subgroup)
					if subgroup and subgroup >= 1 and subgroup <= 8 then existing[subgroup] = true end
				end
			end
		end

		local ordered = {}
		for _, group in ipairs(groups) do
			local allowed = (not hasNumericFilter) or numericFilter[group]
			local present = (not onlyExisting) or (existing and existing[group])
			if allowed and present then ordered[#ordered + 1] = group end
		end
		return ordered
	end

	local orderedGroups = buildOrderedGroups(true)
	local fallbackLists = {}
	local fallbackGroups = {}
	if IsInRaid and IsInRaid() and GetNumGroupMembers and GetRaidRosterInfo then
		for i = 1, GetNumGroupMembers() do
			local name, _, subgroup = GetRaidRosterInfo(i)
			subgroup = tonumber(subgroup)
			if subgroup and subgroup >= 1 and subgroup <= 8 then
				fallbackGroups[subgroup] = true
				if name then
					fallbackLists[subgroup] = fallbackLists[subgroup] or {}
					fallbackLists[subgroup][#fallbackLists[subgroup] + 1] = name
				end
			end
		end
	end
	local specs = {}
	for _, group in ipairs(orderedGroups) do
		local nameList = sparseLists[group]
		if (type(nameList) ~= "string" or nameList == "") and fallbackLists[group] then nameList = table.concat(fallbackLists[group], ",") end
		if type(nameList) == "string" and nameList ~= "" then
			specs[#specs + 1] = {
				group = group,
				sortMethod = "NAMELIST",
				nameList = nameList,
			}
		elseif fallbackGroups[group] then
			-- Fallback while custom namelists are still warming up (for example right after login/reload).
			specs[#specs + 1] = {
				group = group,
				sortMethod = "INDEX",
				nameList = nil,
			}
		end
	end
	return specs
end

function GF:BuildRaidGroupHeaderSpecs(cfg, sortMethod, useCustomSort)
	if useCustomSort then return GF:BuildDenseCustomGroupSpecs(cfg) end

	local method = tostring(sortMethod or "INDEX"):upper()
	if method ~= "NAME" and method ~= "INDEX" then method = "INDEX" end

	local function trimCsvToken(token)
		local normalized = tostring(token or "")
		normalized = normalized:gsub("^%s+", "")
		normalized = normalized:gsub("%s+$", "")
		return normalized
	end

	local ordering = (cfg and cfg.groupingOrder) or (GFH and GFH.GROUP_ORDER) or "1,2,3,4,5,6,7,8"
	local groups, seen = {}, {}
	if type(ordering) == "string" and ordering ~= "" then
		for token in ordering:gmatch("[^,]+") do
			local num = tonumber(trimCsvToken(token))
			if num and num >= 1 and num <= 8 and not seen[num] then
				seen[num] = true
				groups[#groups + 1] = num
			end
		end
	end
	if #groups == 0 then
		for i = 1, 8 do
			groups[#groups + 1] = i
			seen[i] = true
		end
	else
		for i = 1, 8 do
			if not seen[i] then groups[#groups + 1] = i end
		end
	end

	local numericFilter, hasNumericFilter = {}, false
	if cfg and type(cfg.groupFilter) == "string" and cfg.groupFilter ~= "" then
		for token in cfg.groupFilter:gmatch("[^,]+") do
			local num = tonumber(trimCsvToken(token))
			if num and num >= 1 and num <= 8 then
				numericFilter[num] = true
				hasNumericFilter = true
			end
		end
	end

	local existing = {}
	if IsInRaid and IsInRaid() and GetNumGroupMembers and GetRaidRosterInfo then
		for i = 1, GetNumGroupMembers() do
			local _, _, subgroup = GetRaidRosterInfo(i)
			subgroup = tonumber(subgroup)
			if subgroup and subgroup >= 1 and subgroup <= 8 then existing[subgroup] = true end
		end
	end

	local specs = {}
	for _, group in ipairs(groups) do
		local allowed = (not hasNumericFilter) or numericFilter[group]
		local present = existing[group]
		if allowed and present then specs[#specs + 1] = {
			group = group,
			sortMethod = method,
		} end
	end

	return specs
end

function GF.GetFirstAllowedRaidGroup(cfg)
	local ordering = (cfg and cfg.groupingOrder) or (GFH and GFH.GROUP_ORDER) or "1,2,3,4,5,6,7,8"
	local numericFilter, hasNumericFilter = {}, false
	if cfg and type(cfg.groupFilter) == "string" and cfg.groupFilter ~= "" then
		for token in cfg.groupFilter:gmatch("[^,]+") do
			local num = tonumber((tostring(token):gsub("^%s+", ""):gsub("%s+$", "")))
			if num and num >= 1 and num <= 8 then
				numericFilter[num] = true
				hasNumericFilter = true
			end
		end
	end
	if type(ordering) == "string" and ordering ~= "" then
		for token in ordering:gmatch("[^,]+") do
			local num = tonumber((tostring(token):gsub("^%s+", ""):gsub("%s+$", "")))
			if num and num >= 1 and num <= 8 and ((not hasNumericFilter) or numericFilter[num]) then return num end
		end
	end
	if hasNumericFilter then
		for group = 1, 8 do
			if numericFilter[group] then return group end
		end
	end
	return 1
end

local function resolveGroupNumberSettingEnabled(cfg, def)
	local _, us, gn, defUS, defGN = getGroupNumberConfig(cfg, def)
	local enabled = gn.enabled
	if enabled == nil then enabled = us.showGroup end
	if enabled == nil then enabled = defGN.enabled end
	if enabled == nil then enabled = defUS.showGroup end
	return enabled == true
end

local function resolveGroupNumberEnabled(cfg, def)
	local enabled = resolveGroupNumberSettingEnabled(cfg, def)
	if enabled == true and cfg and cfg.groupIndicator and cfg.groupIndicator.hidePerFrame == true and isGroupByGroup(cfg, def) then
		local gi = cfg.groupIndicator or {}
		local defGI = def and def.groupIndicator or {}
		local giEnabled = gi.enabled
		if giEnabled == nil then giEnabled = defGI.enabled end
		if giEnabled == true then return false end
	end
	return enabled == true
end

local function resolveGroupNumberFormat(cfg, def)
	local _, us, gn, defUS, defGN = getGroupNumberConfig(cfg, def)
	local fmt = gn.format or us.groupFormat or defGN.format or defUS.groupFormat or "GROUP"
	return normalizeGroupNumberFormat(fmt) or "GROUP"
end

local function resolveStatusTextStyle(cfg, def, hc)
	local sc = cfg and cfg.status or {}
	local us = sc.unitStatus or {}
	local defStatus = def and def.status or {}
	local defUS = defStatus.unitStatus or {}
	local defH = (def and def.health) or {}
	return {
		font = us.font or defUS.font or hc.font or defH.font,
		fontSize = us.fontSize or defUS.fontSize or hc.fontSize or defH.fontSize or 12,
		fontOutline = us.fontOutline or defUS.fontOutline or hc.fontOutline or defH.fontOutline or "OUTLINE",
		color = us.color or defUS.color or { 1, 1, 1, 1 },
		anchor = us.anchor or defUS.anchor or "CENTER",
		offset = us.offset or defUS.offset or {},
	}
end

local function resolveGroupNumberStyle(cfg, def, hc)
	local _, us, gn, defUS, defGN = getGroupNumberConfig(cfg, def)
	local defH = (def and def.health) or {}
	return {
		font = gn.font or defGN.font or us.font or defUS.font or hc.font or defH.font,
		fontSize = gn.fontSize or defGN.fontSize or us.fontSize or defUS.fontSize or hc.fontSize or defH.fontSize or 12,
		fontOutline = gn.fontOutline or defGN.fontOutline or us.fontOutline or defUS.fontOutline or hc.fontOutline or defH.fontOutline or "OUTLINE",
		color = gn.color or defGN.color or us.color or defUS.color or { 1, 1, 1, 1 },
		anchor = gn.anchor or defGN.anchor or us.anchor or defUS.anchor or "CENTER",
		offset = gn.offset or defGN.offset or us.offset or defUS.offset or {},
	}
end

local function getGroupIndicatorConfig(cfg, def)
	local gi = cfg and cfg.groupIndicator or {}
	local defGI = def and def.groupIndicator or {}
	return gi, defGI
end

local function resolveGroupIndicatorEnabled(cfg, def)
	local gi, defGI = getGroupIndicatorConfig(cfg, def)
	local enabled = gi.enabled
	if enabled == nil then enabled = defGI.enabled end
	return enabled == true
end

local function resolveGroupIndicatorFormat(cfg, def)
	local gi, defGI = getGroupIndicatorConfig(cfg, def)
	local fmt = gi.format or defGI.format or "GROUP"
	return normalizeGroupNumberFormat(fmt) or "GROUP"
end

local function resolveGroupIndicatorStyle(cfg, def, hc)
	local gi, defGI = getGroupIndicatorConfig(cfg, def)
	local defH = (def and def.health) or {}
	return {
		font = gi.font or defGI.font or hc.font or defH.font,
		fontSize = gi.fontSize or defGI.fontSize or hc.fontSize or defH.fontSize or 12,
		fontOutline = gi.fontOutline or defGI.fontOutline or hc.fontOutline or defH.fontOutline or "OUTLINE",
		color = gi.color or defGI.color or { 1, 1, 1, 1 },
		anchor = gi.anchor or defGI.anchor or "TOPLEFT",
		offset = gi.offset or defGI.offset or {},
	}
end

local function isGroupIndicatorAvailable(cfg, def)
	if not isGroupByGroup(cfg, def) then return false end
	return true
end

local function resolveStatusTextAnchor(anchor)
	local a = tostring(anchor or "CENTER"):upper()
	if a == "LEFT" then return "LEFT", "LEFT", "LEFT" end
	if a == "RIGHT" then return "RIGHT", "RIGHT", "RIGHT" end
	if a == "TOP" then return "TOP", "TOP", "CENTER" end
	if a == "BOTTOM" then return "BOTTOM", "BOTTOM", "CENTER" end
	if a == "TOPLEFT" then return "TOPLEFT", "TOPLEFT", "LEFT" end
	if a == "TOPRIGHT" then return "TOPRIGHT", "TOPRIGHT", "RIGHT" end
	if a == "BOTTOMLEFT" then return "BOTTOMLEFT", "BOTTOMLEFT", "LEFT" end
	if a == "BOTTOMRIGHT" then return "BOTTOMRIGHT", "BOTTOMRIGHT", "RIGHT" end
	return "CENTER", "CENTER", "CENTER"
end

local function applyStatusTextAnchor(st, anchor, offset, scale, parent, fs)
	local target = fs or (st and st.statusText)
	if not target then return end
	local point, relPoint, justify = resolveStatusTextAnchor(anchor)
	local off = offset or {}
	target:ClearAllPoints()
	if Pixel and Pixel.SetPoint then
		Pixel.SetPoint(target, point, parent, relPoint, off.x or 0, off.y or 0)
	else
		target:SetPoint(point, parent, relPoint, roundToPixel(off.x or 0, scale), roundToPixel(off.y or 0, scale))
	end
	if justify and target.SetJustifyH then target:SetJustifyH(justify) end
end

local function applyGroupIndicatorAnchor(fs, anchor, offset, scale, parent)
	if not (fs and parent) then return end
	local point, relPoint
	if GFH and GFH.GetOuterAnchorPoint then
		point, relPoint = GFH.GetOuterAnchorPoint(anchor)
	else
		point, relPoint = resolveStatusTextAnchor(anchor)
	end
	local _, _, justify = resolveStatusTextAnchor(anchor)
	local off = offset or {}
	fs:ClearAllPoints()
	if Pixel and Pixel.SetPoint then
		Pixel.SetPoint(fs, point, parent, relPoint, off.x or 0, off.y or 0)
	else
		fs:SetPoint(point, parent, relPoint, roundToPixel(off.x or 0, scale), roundToPixel(off.y or 0, scale))
	end
	if justify and fs.SetJustifyH then fs:SetJustifyH(justify) end
end

function GF.StopDispelGlowFrame(frame, effect)
	if addon.Glow and addon.Glow.Stop and frame then
		addon.Glow.Stop(frame, DISPEL_GLOW_KEY, true)
		return
	end
	if not (LCG and frame) then return end
	if effect == "SHINE" then
		if LCG.AutoCastGlow_Stop then LCG.AutoCastGlow_Stop(frame, DISPEL_GLOW_KEY) end
	elseif effect == "PROC" then
		if LCG.ProcGlow_Stop then LCG.ProcGlow_Stop(frame, DISPEL_GLOW_KEY) end
	elseif effect == "BLIZZARD" then
		if LCG.ButtonGlow_Stop then LCG.ButtonGlow_Stop(frame) end
	else
		if LCG.PixelGlow_Stop then LCG.PixelGlow_Stop(frame, DISPEL_GLOW_KEY) end
	end
end

function GF.StopDispelGlow(frame, effect, st)
	local previousFrame = st and st._dispelGlowTarget
	if st then
		if not st._dispelGlowActive then return end
		effect = effect or st._dispelGlowEffect
		st._dispelGlowActive = nil
		st._dispelGlowEffect = nil
		st._dispelGlowTarget = nil
	end
	if previousFrame and previousFrame ~= frame then GF.StopDispelGlowFrame(previousFrame, effect) end
	GF.StopDispelGlowFrame(frame, effect)
end

function GF.ResolveDispelGlowTarget(self, st, glowOverOverlay)
	local target = st and (st.barGroup or self) or self
	if glowOverOverlay ~= true then return target, 8 end
	local blizzardContainer = st and st.privateAuraDispels
	local overlayFrame = blizzardContainer
	local overlayLevelOffset = 210
	if not (overlayFrame and overlayFrame.GetFrameLevel) or (overlayFrame.IsShown and not overlayFrame:IsShown()) then
		overlayFrame = st and st.dispelTint
		overlayLevelOffset = 1
	end
	if not (target and overlayFrame and overlayFrame.GetFrameLevel) then return target, 8 end
	if overlayFrame.IsShown and not overlayFrame:IsShown() then return target, 8 end
	local host = st.dispelGlowHost
	if not host then
		host = CreateFrame("Frame", nil, target)
		host:EnableMouse(false)
		st.dispelGlowHost = host
	end
	if host.GetParent and host:GetParent() ~= target then host:SetParent(target) end
	if host.SetAllPoints then
		host:ClearAllPoints()
		host:SetAllPoints(target)
	end
	if host.SetFrameStrata then
		local strata = (overlayFrame.GetFrameStrata and overlayFrame:GetFrameStrata()) or (target.GetFrameStrata and target:GetFrameStrata())
		if strata then host:SetFrameStrata(strata) end
	end
	if host.SetFrameLevel then
		host:SetFrameLevel(GF.ClampFrameLevel((overlayFrame:GetFrameLevel() or 0) + overlayLevelOffset))
	end
	return host, 8
end

local function resolveDispelIndicatorEnabled(cfg, kind)
	local sc = cfg and cfg.status or {}
	local dt = sc.dispelTint or {}
	local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
	local overlay = dt.enabled
	if overlay == nil then overlay = def.enabled ~= false end
	local glow = dt.glowEnabled
	if glow == nil then glow = def.glowEnabled == true end
	return overlay == true or glow == true
end

local function setTextSlot(st, fs, cacheKey, mode, cur, maxv, useShort, percentVal, delimiter, delimiter2, delimiter3, hidePercentSymbol, levelText, missingValue, roundPercent, nameText)
	if not (st and fs) then return end
	if fs.SetAlpha then
		if mode == "DEFICIT" then
			local alpha = missingValue
			if issecretvalue and issecretvalue(alpha) then
				fs:SetAlpha(alpha)
			else
				if alpha == nil then alpha = 0 end
				if type(alpha) ~= "number" then alpha = tonumber(alpha) or 0 end
				fs:SetAlpha(alpha)
			end
		else
			fs:SetAlpha(1)
		end
	end
	local last = st[cacheKey]
	if issecretvalue and issecretvalue(last) then last = nil end
	if mode == "NONE" then
		if last ~= "" then
			st[cacheKey] = ""
			fs:SetText("")
		end
		return
	end
	local text
	if mode == "NAME" then
		text = nameText or ""
	elseif UFHelper and UFHelper.formatText then
		text = UFHelper.formatText(mode, cur, maxv, useShort, percentVal, delimiter, delimiter2, delimiter3, hidePercentSymbol, levelText, missingValue, roundPercent)
	else
		text = tostring(cur or 0)
	end
	if issecretvalue and issecretvalue(text) then
		fs:SetText(text)
		st[cacheKey] = nil
		return
	end
	if last ~= text then
		st[cacheKey] = text
		fs:SetText(text)
	end
end

function GF.ClearDataBarText(st)
	if not st then return end
	if st.dataBarTextLeft then st.dataBarTextLeft:SetText("") end
	if st.dataBarTextCenter then st.dataBarTextCenter:SetText("") end
	if st.dataBarTextRight then st.dataBarTextRight:SetText("") end
	st._lastDataBarTextLeft, st._lastDataBarTextCenter, st._lastDataBarTextRight = nil, nil, nil
end

local function normalizeTextMode(value)
	if value == "CURPERCENTDASH" then return "CURPERCENT" end
	return value
end

local delimiterModeCounts = {
	CURMAX = 1,
	CURPERCENT = 1,
	MAXPERCENT = 1,
	PERCENTMAX = 1,
	PERCENTCUR = 1,
	LEVELPERCENT = 1,
	CURMAXPERCENT = 2,
	PERCENTCURMAX = 2,
	LEVELPERCENTMAX = 2,
	LEVELPERCENTCUR = 2,
	LEVELPERCENTCURMAX = 3,
}

local function textModeDelimiterCount(value)
	local mode = normalizeTextMode(value)
	if type(mode) ~= "string" then return 0 end
	if mode == "PERCENT" or mode == "CURRENT" or mode == "MAX" or mode == "NONE" then return 0 end
	return delimiterModeCounts[mode] or 0
end

local function maxDelimiterCount(leftMode, centerMode, rightMode)
	local count = textModeDelimiterCount(leftMode)
	local centerCount = textModeDelimiterCount(centerMode)
	if centerCount > count then count = centerCount end
	local rightCount = textModeDelimiterCount(rightMode)
	if rightCount > count then count = rightCount end
	return count
end

local function getHealthPercent(unit, cur, maxv, calc)
	if calc and calc.EvaluateCurrentHealthPercent and CurveConstants and CurveConstants.ScaleTo100 then return calc:EvaluateCurrentHealthPercent(CurveConstants.ScaleTo100) end
	return nil
end

local function getPowerPercent(unit, powerEnum, cur, maxv)
	if addon.functions and addon.functions.GetPowerPercent then return addon.functions.GetPowerPercent(unit, powerEnum, cur, maxv, true) end
	if issecretvalue and ((cur and issecretvalue(cur)) or (maxv and issecretvalue(maxv))) then return nil end
	if maxv and maxv > 0 then return (cur or 0) / maxv * 100 end
	return nil
end

local function getSafeLevelText(unit, hideClassText)
	if not unit then return "??" end
	if UnitLevel then
		local lvl = UnitLevel(unit)
		if issecretvalue and issecretvalue(lvl) then return "??" end
		if UFHelper and UFHelper.getUnitLevelText then return UFHelper.getUnitLevelText(unit, lvl, hideClassText) end
		lvl = tonumber(lvl) or 0
		if lvl > 0 then return tostring(lvl) end
	end
	if UFHelper and UFHelper.getUnitLevelText then return UFHelper.getUnitLevelText(unit, nil, hideClassText) end
	return "??"
end

local function getUnitRoleKey(unit)
	if unit == "player" or (UnitIsUnit and GFH.UnsecretBool(UnitIsUnit(unit, "player")) == true) then return GF.GetPlayerEffectiveRoleKey and GF.GetPlayerEffectiveRoleKey() or "NONE" end
	local roleEnum
	if UnitGroupRolesAssignedEnum then roleEnum = UnitGroupRolesAssignedEnum(unit) end
	if roleEnum and Enum and Enum.LFGRole then
		if roleEnum == Enum.LFGRole.Tank then return "TANK" end
		if roleEnum == Enum.LFGRole.Healer then return "HEALER" end
		if roleEnum == Enum.LFGRole.Damage then return "DAMAGER" end
	end
	local role = UnitGroupRolesAssigned and UnitGroupRolesAssigned(unit)
	if role == "TANK" or role == "HEALER" or role == "DAMAGER" then return role end
	return "NONE"
end

function GF.NormalizeViewerRoleFilter(value)
	local token = tostring(value or "ALL"):upper()
	if token == "TANK" or token == "HEALER" or token == "DAMAGER" then return token end
	return "ALL"
end

function GF.GetPlayerEffectiveRoleKey()
	local role = nil
	if UnitGroupRolesAssigned then
		role = UnitGroupRolesAssigned("player")
		if issecretvalue and issecretvalue(role) then role = nil end
	end
	if role == "TANK" or role == "HEALER" or role == "DAMAGER" then return role end
	if GetSpecializationRole and C_SpecializationInfo and C_SpecializationInfo.GetSpecialization then
		local specIndex = C_SpecializationInfo.GetSpecialization()
		if specIndex then
			role = GetSpecializationRole(specIndex)
			if issecretvalue and issecretvalue(role) then role = nil end
			if role == "TANK" or role == "HEALER" or role == "DAMAGER" then return role end
		end
	end
	return "NONE"
end

local function getPlayerSpecId()
	if not (C_SpecializationInfo and C_SpecializationInfo.GetSpecialization) then return nil end
	local specIndex = C_SpecializationInfo.GetSpecialization()
	if not specIndex then return nil end
	if C_SpecializationInfo and C_SpecializationInfo.GetSpecializationInfo then
		local specId = C_SpecializationInfo.GetSpecializationInfo(specIndex)
		return specId
	end
	return nil
end

local function shouldShowPowerForUnit(pcfg, unit, st)
	if not pcfg then return true end
	local roleMode = GFH.SelectionMode(pcfg.showRoles)
	local specMode = GFH.SelectionMode(pcfg.showSpecs)

	if roleMode == "some" then
		local roleKey = getUnitRoleKey(unit)
		if st and st._previewRole and isEditModeActive() then roleKey = st._previewRole end
		return GFH.SelectionContains(pcfg.showRoles, roleKey)
	end

	if roleMode == "none" then
		if specMode ~= "some" then return false end
		local specId = getPlayerSpecId()
		if not specId then return true end
		return GFH.SelectionContains(pcfg.showSpecs, specId)
	end

	if specMode == "some" then
		local specId = getPlayerSpecId()
		if not specId then return true end
		return GFH.SelectionContains(pcfg.showSpecs, specId)
	end
	if specMode == "none" then return false end
	return true
end

local function canShowPowerBySelection(pcfg)
	if not pcfg then return true end
	local roleMode = GFH.SelectionMode(pcfg.showRoles)
	local specMode = GFH.SelectionMode(pcfg.showSpecs)
	if roleMode == "some" then return true end
	if roleMode == "none" then return specMode == "some" end

	if specMode == "none" then return false end
	return true
end

isEditModeActive = function()
	local lib = addon.EditModeLib
	return lib and lib.IsInEditMode and lib:IsInEditMode()
end

function GF._createHealerBuffPlacementDefaults()
	if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.CreateDefaultPlacement then return UF.GroupFramesHealerBuffs.CreateDefaultPlacement() end
	return {
		enabled = false,
		version = 1,
		groupsById = {},
		groupOrder = {},
		rulesById = {},
		ruleOrder = {},
	}
end

function GF.CreateDataBarDefaults()
	return {
		enabled = false,
		position = "BELOW",
		height = 16,
		gap = 0,
		detached = false,
		detachedMatchHealthWidth = true,
		detachedWidth = nil,
		detachedGrowFromCenter = false,
		detachedOffset = { x = 0, y = 0 },
		detachedStrata = nil,
		detachedFrameLevelOffset = 2,
		nameMaxChars = 0,
		color = { 0.18, 0.18, 0.22, 1 },
		useClassColor = false,
		textLeft = "NAME",
		textCenter = "CURMAX",
		textRight = "PERCENT",
		textDelimiter = " ",
		textDelimiterSecondary = " ",
		textDelimiterTertiary = " ",
		fontSize = 12,
		font = "__EQOL_GLOBAL_FONT__",
		fontOutline = "OUTLINE",
		textColor = { 1, 1, 1, 1 },
		useTextClassColor = false,
		offsetLeft = { x = 6, y = 0 },
		offsetCenter = { x = 0, y = 0 },
		offsetRight = { x = -6, y = 0 },
		useShortNumbers = true,
		roundPercent = false,
		hidePercentSymbol = false,
		texture = "SOLID",
	}
end

local DEFAULTS = {
	party = {
		auras = {
			buff = {
				anchorPoint = "BOTTOMRIGHT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFont = "__EQOL_GLOBAL_FONT__",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFont = "__EQOL_GLOBAL_FONT__",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				enabled = true,
				growth = "LEFTUP",
				growthX = "LEFT",
				growthY = "UP",
				helpfulFilterMode = "RAID_IN_COMBAT",
				max = 6,
				perRow = 4,
				showCooldown = true,
				showCooldownText = true,
				showStacks = true,
				showTooltip = false,
				size = 26,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			debuff = {
				anchorPoint = "TOPLEFT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFont = "__EQOL_GLOBAL_FONT__",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFont = "__EQOL_GLOBAL_FONT__",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				displayLargerRoleSpecificDebuffs = true,
				enabled = true,
				growth = "RIGHTDOWN",
				growthX = "RIGHT",
				growthY = "DOWN",
				max = 6,
				perRow = 3,
				showCooldown = true,
				showCooldownText = false,
				showDispelIcon = true,
				showStacks = true,
				showTooltip = false,
				size = 23,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			enabled = true,
			externals = {
				anchorPoint = "CENTER",
				anchorOutside = false,
				borderColor = { 1, 0.25, 0.25, 1 },
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFont = "__EQOL_GLOBAL_FONT__",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 12,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFont = "__EQOL_GLOBAL_FONT__",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				drAnchor = "TOPLEFT",
				drColor = {
					1,
					1,
					1,
					1,
				},
				drFontOutline = "OUTLINE",
				drFontSize = 10,
				drOffset = {
					x = 2,
					y = -2,
				},
				enabled = true,
				glowColor = { 1, 0.25, 0.25, 1 },
				glowEnabled = false,
				glowInset = 0,
				glowStyle = "MARCHING_ANTS",
				growth = "RIGHTDOWN",
				growthX = "RIGHT",
				growthY = "DOWN",
				max = 2,
				perRow = 6,
				showCooldown = true,
				showCooldownText = true,
				showDR = false,
				showStacks = false,
				showTooltip = false,
				size = 30,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			provider = "HARF",
			useAdvancedRaidFrames = true,
		},
		barTexture = "EQOL: Blizzard cropped",
		border = {
			color = {
				1,
				1,
				1,
				1,
			},
			edgeSize = 15,
			enabled = false,
			frameLevelOffset = 10,
			inset = 0,
			offset = 2,
			strata = "",
			texture = "Blizzard Dialog",
		},
		groupBorder = {
			color = {
				1,
				1,
				1,
				1,
			},
			edgeSize = 15,
			enabled = false,
			frameLevelOffset = 10,
			inset = 0,
			offset = 2,
			strata = "",
			texture = "Blizzard Dialog",
		},
		customSort = {
			classOrder = {
				"DEATHKNIGHT",
				"DEMONHUNTER",
				"DRUID",
				"EVOKER",
				"HUNTER",
				"MAGE",
				"MONK",
				"PALADIN",
				"PRIEST",
				"ROGUE",
				"SHAMAN",
				"WARLOCK",
				"WARRIOR",
			},
			enabled = true,
			playerFirstInRole = true,
			roleOrder = {
				"TANK",
				"HEALER",
				"DAMAGER",
			},
			separateMeleeRanged = false,
		},
		enabled = false,
		groupIndicator = {
			anchor = "TOPLEFT",
			color = {
				1,
				1,
				1,
				1,
			},
			enabled = false,
			font = "Friz Quadrata TT",
			fontOutline = "OUTLINE",
			fontSize = 12,
			format = "GROUP",
			hidePerFrame = false,
			offset = {
				x = 0,
				y = 0,
			},
		},
		growth = "UP",
		healerBuffPlacement = {
			_eqolNormalized = true,
			_eqolUnified = true,
			enabled = true,
			groupOrder = {
				"13",
			},
			groupsById = {
				["13"] = {
					anchorPoint = "CENTER",
					barDrainAnimation = false,
					barOrientation = "HORIZONTAL",
					barReverseFill = false,
					barThickness = 6,
					borderSize = 2,
					borderStrata = nil,
					borderFrameLevelOffset = 4,
					color = {
						1,
						0.82,
						0.1,
						0.9,
					},
					growth = "RIGHTDOWN",
					hideChargeText = false,
					hideCooldownText = false,
					iconMode = "ALL",
					id = "13",
					indicatorBorderColor = {
						0,
						0,
						0,
						0.95,
					},
					indicatorBorderEnabled = false,
					indicatorBorderOffset = 0,
					indicatorBorderSize = 1,
					indicatorBorderTexture = "DEFAULT",
					inset = 0,
					max = 3,
					name = "Indicator 13",
					perRow = 3,
					ruleMatch = "ANY",
					showCooldownBling = true,
					showCooldownEdge = true,
					showCooldownSwipe = true,
					size = 36,
					spacing = 0,
					style = "ICON",
					x = 0,
					y = 0,
				},
			},
			ruleOrder = {
				"57",
				"60",
				"61",
				"62",
				"63",
				"64",
			},
			rulesById = {
				["57"] = {
					_pseudoAuraInstanceId = -700057,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "57",
					["not"] = true,
					spellFamilyId = "druid_mark_of_the_wild",
				},
				["60"] = {
					_pseudoAuraInstanceId = -700060,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "60",
					["not"] = true,
					spellFamilyId = "evoker_blessing_of_the_bronze",
				},
				["61"] = {
					_pseudoAuraInstanceId = -700061,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "61",
					["not"] = true,
					spellFamilyId = "mage_arcane_intellect",
				},
				["62"] = {
					_pseudoAuraInstanceId = -700062,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "62",
					["not"] = true,
					spellFamilyId = "priest_power_word_fortitude",
				},
				["63"] = {
					_pseudoAuraInstanceId = -700063,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "63",
					["not"] = true,
					spellFamilyId = "warrior_battle_shout",
				},
				["64"] = {
					_pseudoAuraInstanceId = -700064,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "64",
					["not"] = true,
					spellFamilyId = "shaman_skyfury",
				},
			},
			version = 1,
		},
		health = {
			absorbColor = {
				1,
				0.95294123888016,
				0,
				1,
			},
			absorbEnabled = true,
			absorbDontOverflowHealthBar = false,
			absorbOverlayHeight = 300,
			absorbReverseFill = true,
			absorbTexture = "EQOL: Absorb",
			absorbUseCustomColor = true,
			useAbsorbGlow = false,
			backdrop = {
				clampToFill = false,
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			color = {
				0.035294119268656,
				0.42352944612503,
				0.74901962280273,
				1,
			},
			font = "__EQOL_GLOBAL_FONT__",
			fontOutline = "OUTLINE",
			fontSize = 12,
			healAbsorbColor = {
				1,
				0.3,
				0.3,
				0.7,
			},
			healAbsorbEnabled = true,
			healAbsorbDontOverflowHealthBar = true,
			healAbsorbOverlayHeight = 158,
			healAbsorbReverseFill = false,
			healAbsorbTexture = "EQOL: Absorb",
			healAbsorbUseCustomColor = true,
			hidePercentSymbol = false,
			incomingHealColor = {
				0.2000000178813934,
				0.85098046064376831,
				0.3490196168422699,
				0.58593714237213135,
			},
			incomingHealEnabled = false,
			incomingHealTexture = "DEFAULT",
			offsetCenter = {
				x = 0,
				y = 0,
			},
			offsetLeft = {
				x = 6,
				y = 0,
			},
			offsetRight = {
				x = -6,
				y = 0,
			},
			showSampleAbsorb = false,
			showSampleHealAbsorb = true,
			showSampleIncomingHeal = false,
			tempMaxHealthLossEnabled = true,
			textCenter = "PERCENT",
			textColor = {
				1,
				1,
				1,
				1,
			},
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "NONE",
			texture = "DEFAULT",
			smoothFill = false,
			useClassColor = true,
			useCustomColor = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		dataBar = GF.CreateDataBarDefaults(),
		height = 100,
		hideInClientScene = true,
		highlight = {
			aggro = false,
			color = {
				1,
				0,
				0,
				1,
			},
			enabled = false,
			mouseover = true,
			size = 2,
			texture = "DEFAULT",
		},
		highlightAggro = {
			color = {
				1,
				0.45490199327468872,
				0,
				1,
			},
			enabled = true,
			layer = "ABOVE_BORDER",
			mode = "ALL",
			offset = 6,
			sample = false,
			size = 29,
			texture = "Blizzard Tooltip",
		},
		highlightHover = {
			color = {
				1,
				1,
				1,
				0.9,
			},
			enabled = true,
			offset = 0,
			size = 2,
			strata = nil,
			texture = "DEFAULT",
		},
		highlightTarget = {
			color = {
				1,
				1,
				0,
				1,
			},
			enabled = false,
			layer = "ABOVE_BORDER",
			offset = 0,
			size = 2,
			texture = "DEFAULT",
		},
		partyCenterOutward = false,
		point = "CENTER",
		portrait = {
			borderWithFrame = true,
			detached = false,
			detachedSize = nil,
			enabled = false,
			mode = "PORTRAIT",
			separator = {
				color = {
					1,
					0.55294120311737061,
					0,
					1,
				},
				enabled = false,
				size = 1,
				texture = "SOLID",
				useCustomColor = false,
			},
			side = "LEFT",
			squareBackground = true,
		},
		power = {
			backdrop = {
				clampToFill = false,
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			detached = false,
			detachedBorder = false,
			detachedBorderOffset = nil,
			detachedBorderSize = nil,
			detachedBorderTexture = nil,
			detachedFrameLevelOffset = 2,
			detachedGrowFromCenter = false,
			detachedHeight = nil,
			detachedMatchHealthWidth = true,
			detachedStrata = nil,
			offset = {
				x = 0,
				y = 0,
			},
			width = nil,
			font = "__EQOL_GLOBAL_FONT__",
			fontOutline = "OUTLINE",
			fontSize = 10,
			hidePercentSymbol = false,
			offsetCenter = {
				x = 0,
				y = 0,
			},
			offsetLeft = {
				x = 6,
				y = 0,
			},
			offsetRight = {
				x = -6,
				y = 0,
			},
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			showSpecs = {},
			textCenter = "NONE",
			textColor = {
				1,
				1,
				1,
				1,
			},
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "NONE",
			texture = "DEFAULT",
			smoothFill = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		powerHeight = 6,
		privateAuras = {
			countdownFrame = true,
			countdownNumbers = false,
			showTooltip = false,
			duration = {
				enable = false,
				offsetX = 0,
				offsetY = -1,
				point = "BOTTOM",
			},
			enabled = true,
			icon = {
				amount = 4,
				offset = 0,
				point = "LEFT",
				size = 30,
			},
			parent = {
				offsetX = 0,
				offsetY = 0,
				point = "TOPRIGHT",
			},
			showDispelType = true,
		},
		relativePoint = "CENTER",
		relativeTo = "UIParent",
		roleIcon = {
			enabled = true,
			point = "TOPLEFT",
			relativePoint = "TOPLEFT",
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			size = 16,
			spacing = 2,
			style = "TINY",
			x = 2,
			y = -2,
		},
		showPlayer = true,
		showSolo = true,
		sortDir = "DESC",
		sortMethod = "NAMELIST",
		spacing = 0,
		status = {
			assistIcon = {
				enabled = true,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				size = 12,
				x = 18,
				y = -2,
			},
			dispelTint = {
				alpha = 1,
				enabled = true,
				fillAlpha = 0.199999988079071,
				fillColor = {
					0,
					0,
					0,
					1,
				},
				fillEnabled = true,
				glowColor = {
					1,
					1,
					1,
					1,
				},
				glowColorMode = "DISPEL",
				glowEffect = "PIXEL",
				glowEnabled = true,
				glowFrequency = 0.25,
				glowInset = 33,
				glowLines = 8,
				glowOverOverlay = false,
				glowThickness = 3,
				glowX = 0,
				glowY = 0,
				showSample = false,
			},
			groupNumber = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = false,
				font = "Friz Quadrata TT",
				fontOutline = "OUTLINE",
				fontSize = 12,
				format = "GROUP",
				offset = {
					x = 0,
					y = 0,
				},
			},
			hideLevelAtMax = true,
			leaderIcon = {
				enabled = true,
				point = "TOPRIGHT",
				relativePoint = "TOPRIGHT",
				size = 19,
				x = 6,
				y = 10,
			},
			raidAssignmentIcon = {
				enabled = false,
				point = "TOPRIGHT",
				relativePoint = "TOPRIGHT",
				size = 12,
				x = -2,
				y = -2,
			},
			levelAnchor = "TOPRIGHT",
			levelColor = {
				1,
				0.85,
				0,
				1,
			},
			levelColorMode = "CUSTOM",
			levelEnabled = true,
			levelFont = "__EQOL_GLOBAL_FONT__",
			levelFontOutline = "OUTLINE",
			levelFontSize = 12,
			levelOffset = {
				x = -6,
				y = -4,
			},
			nameColor = {
				1,
				1,
				1,
				1,
			},
			nameColorMode = "CLASS",
			phaseIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 40,
				x = 0,
				y = 0,
			},
			raidIcon = {
				enabled = true,
				point = "TOP",
				relativePoint = "TOP",
				size = 18,
				x = 0,
				y = -1,
			},
			rangeFade = {
				alpha = 0.55,
				enabled = true,
				offlineAlpha = 0.4,
			},
			readyCheckIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 30,
				x = 0,
				y = 0,
			},
			resurrectIcon = {
				enabled = true,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				sample = false,
				size = 34,
				x = 0,
				y = 0,
			},
			summonIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 40,
				x = 0,
				y = 0,
			},
			unitStatus = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = true,
				font = "__EQOL_GLOBAL_FONT__",
				fontOutline = "OUTLINE",
				fontSize = 12,
				groupFormat = "GROUP",
				hideHealthTextWhenOffline = true,
				offset = {
					x = 0,
					y = 0,
				},
				showAFK = false,
				showDead = true,
				showDND = false,
				showGhost = true,
				showGroup = false,
				showOffline = true,
			},
		},
		text = {
			font = "__EQOL_GLOBAL_FONT__",
			fontOutline = "OUTLINE",
			fontSize = 15,
			nameAnchor = "TOP",
			nameMaxChars = 15,
			nameNoEllipsis = true,
			nameOffset = {
				x = 0,
				y = -4,
			},
			showHealthPercent = true,
			showName = true,
			showPowerPercent = false,
			useClassColor = true,
		},
		tooltip = {
			mode = "MODIFIER",
			modifier = "SHIFT",
			useEditMode = true,
		},
		width = 160,
		x = -597,
		y = 105,
	},
	raid = {
		auras = {
			buff = {
				anchorPoint = "TOPLEFT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "CENTER",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				enabled = true,
				growth = "RIGHTDOWN",
				growthX = "RIGHT",
				growthY = "DOWN",
				helpfulFilterMode = "RAID_IN_COMBAT",
				max = 5,
				perRow = 5,
				showCooldown = true,
				showCooldownText = false,
				showDispelIcon = true,
				showStacks = true,
				showTooltip = false,
				size = 20,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			debuff = {
				anchorPoint = "BOTTOMLEFT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "CENTER",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 0,
					y = 0,
				},
				displayLargerRoleSpecificDebuffs = true,
				enabled = true,
				growth = "RIGHTUP",
				growthX = "RIGHT",
				growthY = "UP",
				max = 5,
				perRow = 5,
				showCooldown = true,
				showCooldownText = false,
				showDispelIcon = false,
				showStacks = true,
				showTooltip = false,
				size = 20,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			enabled = true,
			externals = {
				anchorPoint = "CENTER",
				anchorOutside = false,
				borderColor = { 1, 0.25, 0.25, 1 },
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 12,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				drAnchor = "TOPLEFT",
				drColor = {
					1,
					1,
					1,
					1,
				},
				drFontOutline = "OUTLINE",
				drFontSize = 10,
				drOffset = {
					x = 2,
					y = -2,
				},
				enabled = true,
				glowColor = { 1, 0.25, 0.25, 1 },
				glowEnabled = false,
				glowInset = 0,
				glowStyle = "MARCHING_ANTS",
				growth = "RIGHTDOWN",
				growthX = "RIGHT",
				growthY = "DOWN",
				max = 2,
				perRow = 2,
				showCooldown = true,
				showCooldownText = false,
				showDR = false,
				showStacks = false,
				showTooltip = false,
				size = 20,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			provider = "HARF",
			useAdvancedRaidFrames = true,
		},
		barTexture = "SOLID",
		border = {
			color = {
				0,
				0,
				0,
				1,
			},
			edgeSize = 1,
			enabled = true,
			frameLevelOffset = 3,
			inset = 0,
			offset = 0,
			strata = "",
			texture = "DEFAULT",
		},
		columnSpacing = 0,
		customSort = {
			classOrder = {
				"DEATHKNIGHT",
				"DEMONHUNTER",
				"DRUID",
				"EVOKER",
				"HUNTER",
				"MAGE",
				"MONK",
				"PALADIN",
				"PRIEST",
				"ROGUE",
				"SHAMAN",
				"WARLOCK",
				"WARRIOR",
			},
			enabled = true,
			playerFirstInRole = false,
			roleOrder = {
				"TANK",
				"HEALER",
				"MELEE",
				"RANGED",
			},
			separateMeleeRanged = true,
		},
		dynamicContentScale = true,
		dynamicScaleMin = 0.800000011920929,
		enabled = false,
		groupBy = "ASSIGNEDROLE",
		groupGrowth = "DOWN",
		groupIndicator = {
			anchor = "LEFT",
			color = {
				1,
				1,
				1,
				1,
			},
			enabled = true,
			font = "__EQOL_GLOBAL_FONT__",
			fontOutline = "OUTLINE",
			fontSize = 20,
			format = "GROUP",
			hidePerFrame = false,
			offset = {
				x = 0,
				y = 0,
			},
		},
		groupingOrder = "TANK,HEALER,DAMAGER",
		groupsPerRow = 8,
		growth = "RIGHT",
		healerBuffPlacement = {
			_eqolNormalized = true,
			_eqolUnified = true,
			enabled = true,
			groupOrder = {
				"13",
			},
			groupsById = {
				["13"] = {
					anchorPoint = "CENTER",
					barDrainAnimation = false,
					barOrientation = "HORIZONTAL",
					barReverseFill = false,
					barThickness = 6,
					borderSize = 2,
					borderStrata = nil,
					borderFrameLevelOffset = 4,
					color = {
						1,
						0.82,
						0.1,
						0.9,
					},
					growth = "RIGHTDOWN",
					hideChargeText = false,
					hideCooldownText = false,
					iconMode = "ALL",
					id = "13",
					indicatorBorderColor = {
						0,
						0,
						0,
						0.95,
					},
					indicatorBorderEnabled = false,
					indicatorBorderOffset = 0,
					indicatorBorderSize = 1,
					indicatorBorderTexture = "DEFAULT",
					inset = 0,
					max = 3,
					name = "Indicator 13",
					perRow = 3,
					ruleMatch = "ANY",
					showCooldownBling = true,
					showCooldownEdge = true,
					showCooldownSwipe = true,
					size = 36,
					spacing = 0,
					style = "ICON",
					x = 0,
					y = 0,
				},
			},
			ruleOrder = {
				"57",
				"60",
				"61",
				"62",
				"63",
				"64",
			},
			rulesById = {
				["57"] = {
					_pseudoAuraInstanceId = -700057,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "57",
					["not"] = true,
					spellFamilyId = "druid_mark_of_the_wild",
				},
				["60"] = {
					_pseudoAuraInstanceId = -700060,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "60",
					["not"] = true,
					spellFamilyId = "evoker_blessing_of_the_bronze",
				},
				["61"] = {
					_pseudoAuraInstanceId = -700061,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "61",
					["not"] = true,
					spellFamilyId = "mage_arcane_intellect",
				},
				["62"] = {
					_pseudoAuraInstanceId = -700062,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "62",
					["not"] = true,
					spellFamilyId = "priest_power_word_fortitude",
				},
				["63"] = {
					_pseudoAuraInstanceId = -700063,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "63",
					["not"] = true,
					spellFamilyId = "warrior_battle_shout",
				},
				["64"] = {
					_pseudoAuraInstanceId = -700064,
					appliesParty = true,
					appliesRaid = true,
					desaturateMissing = true,
					enabled = true,
					groupId = "13",
					id = "64",
					["not"] = true,
					spellFamilyId = "shaman_skyfury",
				},
			},
			version = 1,
		},
		health = {
			absorbColor = {
				1,
				0.81960791349411,
				0.14901961386204,
				1,
			},
			absorbEnabled = true,
			absorbDontOverflowHealthBar = false,
			absorbReverseFill = true,
			absorbTexture = "EQOL: Absorb",
			absorbUseCustomColor = true,
			useAbsorbGlow = false,
			backdrop = {
				clampToFill = false,
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			color = {
				0,
				0.8,
				0,
				1,
			},
			fontOutline = "OUTLINE",
			fontSize = 12,
			healAbsorbColor = {
				1,
				0.3,
				0.3,
				0.7,
			},
			healAbsorbEnabled = true,
			healAbsorbDontOverflowHealthBar = true,
			healAbsorbReverseFill = false,
			healAbsorbTexture = "EQOL: Absorb",
			healAbsorbUseCustomColor = false,
			hidePercentSymbol = false,
			incomingHealColor = {
				0.2,
				0.85,
				0.35,
				0.45,
			},
			incomingHealEnabled = false,
			offsetCenter = {
				x = 0,
				y = 20,
			},
			offsetLeft = {
				x = 5,
				y = 0,
			},
			offsetRight = {
				x = -5,
				y = 0,
			},
			showSampleAbsorb = false,
			showSampleHealAbsorb = false,
			showSampleIncomingHeal = false,
			tempMaxHealthLossEnabled = true,
			textCenter = "PERCENT",
			textColor = {
				1,
				1,
				1,
				1,
			},
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "CURRENT",
			texture = "DEFAULT",
			smoothFill = false,
			useClassColor = true,
			useCustomColor = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		dataBar = GF.CreateDataBarDefaults(),
		height = 100,
		hideInClientScene = true,
		highlight = {
			aggro = false,
			color = {
				1,
				0,
				0,
				1,
			},
			enabled = false,
			mouseover = true,
			size = 2,
			texture = "DEFAULT",
		},
		highlightAggro = {
			color = {
				1,
				0,
				0,
				1,
			},
			enabled = false,
			layer = "ABOVE_BORDER",
			mode = "ALL",
			offset = 0,
			sample = false,
			size = 2,
			texture = "DEFAULT",
		},
		highlightHover = {
			color = {
				1,
				1,
				1,
				0.9,
			},
			enabled = false,
			offset = 0,
			size = 2,
			strata = nil,
			texture = "DEFAULT",
		},
		highlightTarget = {
			color = {
				1,
				1,
				0,
				1,
			},
			enabled = false,
			layer = "ABOVE_BORDER",
			offset = 0,
			size = 2,
			texture = "DEFAULT",
		},
		maxColumns = 4,
		point = "LEFT",
		portrait = {
			borderWithFrame = false,
			enabled = false,
			mode = "PORTRAIT",
			separator = {
				enabled = true,
				texture = "SOLID",
			},
			side = "LEFT",
			squareBackground = false,
		},
		power = {
			backdrop = {
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			fontOutline = "OUTLINE",
			fontSize = 9,
			hidePercentSymbol = false,
			offsetCenter = {
				x = 0,
				y = 0,
			},
			offsetLeft = {
				x = 5,
				y = 0,
			},
			offsetRight = {
				x = -5,
				y = 0,
			},
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			showSpecs = {},
			textCenter = "NONE",
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "NONE",
			texture = "DEFAULT",
			smoothFill = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		powerHeight = 4,
		privateAuras = {
			countdownFrame = false,
			countdownNumbers = false,
			showTooltip = false,
			duration = {
				enable = false,
				offsetX = 0,
				offsetY = -1,
				point = "BOTTOM",
			},
			enabled = true,
			icon = {
				amount = 2,
				offset = 0,
				point = "BOTTOM",
				size = 18,
			},
			parent = {
				offsetX = 19,
				offsetY = 9,
				point = "LEFT",
			},
			showDispelType = false,
		},
		relativePoint = "LEFT",
		relativeTo = "UIParent",
		roleIcon = {
			enabled = true,
			point = "TOPLEFT",
			relativePoint = "TOPLEFT",
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			size = 16,
			spacing = 2,
			style = "TINY",
			x = 2,
			y = -2,
		},
		sortDir = "ASC",
		sortMethod = "NAMELIST",
		spacing = 0,
		status = {
			assistIcon = {
				enabled = false,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				size = 10,
				x = 14,
				y = -1,
			},
			dispelTint = {
				alpha = 0.25,
				enabled = false,
				fillAlpha = 0.2,
				fillColor = {
					0,
					0,
					0,
					1,
				},
				fillEnabled = true,
				glowColor = {
					1,
					1,
					1,
					1,
				},
				glowColorMode = "DISPEL",
				glowEffect = "PIXEL",
				glowEnabled = false,
				glowFrequency = 0.25,
				glowLines = 8,
				glowOverOverlay = false,
				glowThickness = 3,
				glowX = 0,
				glowY = 0,
				showSample = false,
			},
			groupNumber = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = false,
				fontOutline = "OUTLINE",
				fontSize = 12,
				format = "GROUP",
				offset = {
					x = 0,
					y = 0,
				},
			},
			hideLevelAtMax = true,
			leaderIcon = {
				enabled = true,
				point = "TOPRIGHT",
				relativePoint = "TOPRIGHT",
				size = 19,
				x = 6,
				y = 10,
			},
			levelAnchor = "TOPRIGHT",
			levelColor = {
				1,
				0.85,
				0,
				1,
			},
			levelColorMode = "CUSTOM",
			levelEnabled = false,
			levelFont = "__EQOL_GLOBAL_FONT__",
			levelFontOutline = "OUTLINE",
			levelFontSize = 12,
			levelOffset = {
				x = -6,
				y = -4,
			},
			nameColor = {
				1,
				1,
				1,
				1,
			},
			nameColorMode = "CLASS",
			phaseIcon = {
				enabled = false,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				sample = false,
				size = 14,
				x = 0,
				y = 0,
			},
			raidIcon = {
				enabled = true,
				point = "TOP",
				relativePoint = "TOP",
				size = 18,
				x = 0,
				y = 12,
			},
			rangeFade = {
				alpha = 0.55,
				enabled = true,
				offlineAlpha = 0.4,
			},
			readyCheckIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			resurrectIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			summonIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			unitStatus = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = true,
				fontOutline = "OUTLINE",
				fontSize = 12,
				groupFormat = "GROUP",
				hideHealthTextWhenOffline = true,
				offset = {
					x = 0,
					y = 0,
				},
				showAFK = false,
				showDead = true,
				showDND = false,
				showGhost = true,
				showGroup = false,
				showOffline = true,
			},
		},
		text = {
			font = "__EQOL_GLOBAL_FONT__",
			fontOutline = "OUTLINE",
			fontSize = 15,
			nameAnchor = "CENTER",
			nameMaxChars = 15,
			nameNoEllipsis = true,
			nameOffset = {
				x = 0,
				y = 0,
			},
			showHealthPercent = false,
			showName = true,
			showPowerPercent = false,
			useClassColor = true,
		},
		tooltip = {
			mode = "OFF",
			modifier = "ALT",
			useEditMode = false,
		},
		unitsPerColumn = 5,
		width = 120,
		x = 278,
		y = 184,
	},
	mt = {
		auras = {
			buff = {
				anchorPoint = "TOPLEFT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				enabled = false,
				growth = "DOWNRIGHT",
				growthX = "RIGHT",
				growthY = "DOWN",
				helpfulFilterMode = "RAID_IN_COMBAT",
				max = 6,
				perRow = 3,
				showCooldown = true,
				showCooldownText = false,
				showDispelIcon = true,
				showStacks = true,
				showTooltip = false,
				size = 26,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			debuff = {
				anchorPoint = "BOTTOMLEFT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				displayLargerRoleSpecificDebuffs = true,
				enabled = false,
				growth = "DOWNRIGHT",
				growthX = "RIGHT",
				growthY = "DOWN",
				max = 6,
				perRow = 3,
				showCooldown = true,
				showCooldownText = false,
				showDispelIcon = true,
				showStacks = true,
				showTooltip = false,
				size = 26,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			enabled = false,
			externals = {
				anchorPoint = "CENTER",
				anchorOutside = false,
				borderColor = { 1, 0.25, 0.25, 1 },
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 12,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				drAnchor = "TOPLEFT",
				drColor = {
					1,
					1,
					1,
					1,
				},
				drFontOutline = "OUTLINE",
				drFontSize = 10,
				drOffset = {
					x = 2,
					y = -2,
				},
				enabled = false,
				glowColor = { 1, 0.25, 0.25, 1 },
				glowEnabled = false,
				glowInset = 0,
				glowStyle = "MARCHING_ANTS",
				growth = "RIGHTDOWN",
				growthX = "RIGHT",
				growthY = "DOWN",
				max = 2,
				perRow = 6,
				showCooldown = true,
				showCooldownText = true,
				showDR = false,
				showStacks = false,
				showTooltip = false,
				size = 34,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			provider = "HARF",
			useAdvancedRaidFrames = true,
		},
		barTexture = "SOLID",
		border = {
			color = {
				0,
				0,
				0,
				0.8,
			},
			edgeSize = 1,
			enabled = false,
			frameLevelOffset = 3,
			inset = 0,
			offset = 0,
			strata = "",
			texture = "DEFAULT",
		},
		columnSpacing = 8,
		customSort = {
			classOrder = {
				"DEATHKNIGHT",
				"DEMONHUNTER",
				"DRUID",
				"EVOKER",
				"HUNTER",
				"MAGE",
				"MONK",
				"PALADIN",
				"PRIEST",
				"ROGUE",
				"SHAMAN",
				"WARLOCK",
				"WARRIOR",
			},
			enabled = false,
			playerFirstInRole = false,
			roleOrder = {
				"TANK",
				"HEALER",
				"DAMAGER",
			},
			separateMeleeRanged = false,
		},
		dynamicContentScale = false,
		dynamicScaleMin = 0,
		enabled = false,
		groupGrowth = "DOWN",
		groupIndicator = {
			anchor = "TOPLEFT",
			color = {
				1,
				1,
				1,
				1,
			},
			enabled = false,
			fontOutline = "OUTLINE",
			fontSize = 12,
			format = "GROUP",
			hidePerFrame = false,
			offset = {
				x = 0,
				y = 0,
			},
		},
		growth = "DOWN",
		healerBuffPlacement = {
			_eqolNormalized = true,
			enabled = false,
			groupOrder = {},
			groupsById = {},
			ruleOrder = {},
			rulesById = {},
			version = 1,
		},
		health = {
			absorbColor = {
				0.85,
				0.95,
				1,
				0.7,
			},
			absorbEnabled = true,
			absorbDontOverflowHealthBar = false,
			absorbReverseFill = false,
			absorbTexture = "SOLID",
			absorbUseCustomColor = false,
			useAbsorbGlow = false,
			backdrop = {
				clampToFill = false,
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			color = {
				0,
				0.8,
				0,
				1,
			},
			fontOutline = "OUTLINE",
			fontSize = 12,
			healAbsorbColor = {
				1,
				0.3,
				0.3,
				0.7,
			},
			healAbsorbEnabled = true,
			healAbsorbDontOverflowHealthBar = true,
			healAbsorbReverseFill = true,
			healAbsorbTexture = "SOLID",
			healAbsorbUseCustomColor = false,
			hidePercentSymbol = false,
			incomingHealColor = {
				0.2,
				0.85,
				0.35,
				0.45,
			},
			incomingHealEnabled = false,
			offsetCenter = {
				x = 0,
				y = 0,
			},
			offsetLeft = {
				x = 5,
				y = 0,
			},
			offsetRight = {
				x = -5,
				y = 0,
			},
			showSampleAbsorb = false,
			showSampleHealAbsorb = false,
			showSampleIncomingHeal = false,
			tempMaxHealthLossEnabled = true,
			textCenter = "PERCENT",
			textColor = {
				1,
				1,
				1,
				1,
			},
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "CURRENT",
			texture = "DEFAULT",
			smoothFill = false,
			useClassColor = true,
			useCustomColor = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		height = 100,
		hideInClientScene = true,
		highlight = {
			aggro = false,
			color = {
				1,
				0,
				0,
				1,
			},
			enabled = false,
			mouseover = true,
			size = 2,
			texture = "DEFAULT",
		},
		highlightAggro = {
			color = {
				1,
				0,
				0,
				1,
			},
			enabled = false,
			layer = "ABOVE_BORDER",
			mode = "ALL",
			offset = 0,
			sample = false,
			size = 2,
			texture = "DEFAULT",
		},
		highlightHover = {
			color = {
				1,
				1,
				1,
				0.9,
			},
			enabled = false,
			offset = 0,
			size = 2,
			strata = nil,
			texture = "DEFAULT",
		},
		highlightTarget = {
			color = {
				1,
				1,
				0,
				1,
			},
			enabled = false,
			layer = "ABOVE_BORDER",
			offset = 0,
			size = 2,
			texture = "DEFAULT",
		},
		maxColumns = 1,
		point = "TOPLEFT",
		portrait = {
			borderWithFrame = false,
			enabled = false,
			mode = "PORTRAIT",
			separator = {
				enabled = true,
				texture = "SOLID",
			},
			side = "LEFT",
			squareBackground = false,
		},
		power = {
			backdrop = {
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			fontOutline = "OUTLINE",
			fontSize = 9,
			hidePercentSymbol = false,
			offsetCenter = {
				x = 0,
				y = 0,
			},
			offsetLeft = {
				x = 5,
				y = 0,
			},
			offsetRight = {
				x = -5,
				y = 0,
			},
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			showSpecs = {},
			textCenter = "NONE",
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "NONE",
			texture = "DEFAULT",
			smoothFill = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		powerHeight = 6,
		privateAuras = {
			countdownFrame = true,
			countdownNumbers = false,
			showTooltip = false,
			duration = {
				enable = false,
				offsetX = 0,
				offsetY = -1,
				point = "BOTTOM",
			},
			enabled = false,
			icon = {
				amount = 2,
				offset = 2,
				point = "LEFT",
				size = 18,
			},
			parent = {
				offsetX = 0,
				offsetY = 0,
				point = "CENTER",
			},
			showDispelType = false,
		},
		relativePoint = "TOPLEFT",
		relativeTo = "UIParent",
		roleIcon = {
			enabled = true,
			point = "TOPLEFT",
			relativePoint = "TOPLEFT",
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			size = 16,
			spacing = 2,
			style = "TINY",
			x = 2,
			y = -2,
		},
		sortDir = "ASC",
		sortMethod = "NAME",
		spacing = 1,
		status = {
			assistIcon = {
				enabled = true,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				size = 10,
				x = 14,
				y = -1,
			},
			dispelTint = {
				alpha = 0.25,
				enabled = true,
				fillAlpha = 0.2,
				fillColor = {
					0,
					0,
					0,
					1,
				},
				fillEnabled = true,
				glowColor = {
					1,
					1,
					1,
					1,
				},
				glowColorMode = "DISPEL",
				glowEffect = "PIXEL",
				glowEnabled = false,
				glowFrequency = 0.25,
				glowLines = 8,
				glowOverOverlay = false,
				glowThickness = 3,
				glowX = 0,
				glowY = 0,
				showSample = false,
			},
			groupNumber = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = false,
				fontOutline = "OUTLINE",
				fontSize = 12,
				format = "GROUP",
				offset = {
					x = 0,
					y = 0,
				},
			},
			hideLevelAtMax = true,
			leaderIcon = {
				enabled = true,
				point = "TOPRIGHT",
				relativePoint = "TOPRIGHT",
				size = 19,
				x = 6,
				y = 10,
			},
			levelAnchor = "TOPRIGHT",
			levelColor = {
				1,
				0.85,
				0,
				1,
			},
			levelColorMode = "CUSTOM",
			levelEnabled = true,
			levelFontOutline = "OUTLINE",
			levelFontSize = 12,
			levelOffset = {
				x = -6,
				y = -4,
			},
			nameColor = {
				1,
				1,
				1,
				1,
			},
			nameColorMode = "CLASS",
			phaseIcon = {
				enabled = false,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				sample = false,
				size = 14,
				x = 0,
				y = 0,
			},
			raidIcon = {
				enabled = true,
				point = "TOP",
				relativePoint = "TOP",
				size = 18,
				x = 0,
				y = 12,
			},
			rangeFade = {
				alpha = 0.55,
				enabled = true,
				offlineAlpha = 0.4,
			},
			readyCheckIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			resurrectIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			summonIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			unitStatus = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = true,
				fontOutline = "OUTLINE",
				fontSize = 12,
				groupFormat = "GROUP",
				hideHealthTextWhenOffline = false,
				offset = {
					x = 0,
					y = 0,
				},
				showAFK = false,
				showDead = true,
				showDND = false,
				showGhost = true,
				showGroup = false,
				showOffline = true,
			},
		},
		text = {
			fontOutline = "OUTLINE",
			fontSize = 15,
			nameAnchor = "TOP",
			nameMaxChars = 15,
			nameNoEllipsis = false,
			nameOffset = {
				x = 0,
				y = -4,
			},
			showHealthPercent = false,
			showName = true,
			showPowerPercent = false,
			useClassColor = true,
		},
		tooltip = {
			mode = "OFF",
			modifier = "ALT",
			useEditMode = false,
		},
		unitsPerColumn = 2,
		width = 180,
		x = 500,
		y = -120,
	},
	ma = {
		auras = {
			buff = {
				anchorPoint = "TOPLEFT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				enabled = false,
				growth = "DOWNRIGHT",
				growthX = "RIGHT",
				growthY = "DOWN",
				helpfulFilterMode = "RAID_IN_COMBAT",
				max = 6,
				perRow = 3,
				showCooldown = true,
				showCooldownText = false,
				showDispelIcon = true,
				showStacks = true,
				showTooltip = false,
				size = 26,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			debuff = {
				anchorPoint = "BOTTOMLEFT",
				anchorOutside = false,
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 8,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				displayLargerRoleSpecificDebuffs = true,
				enabled = false,
				growth = "DOWNRIGHT",
				growthX = "RIGHT",
				growthY = "DOWN",
				max = 6,
				perRow = 3,
				showCooldown = true,
				showCooldownText = false,
				showDispelIcon = true,
				showStacks = true,
				showTooltip = false,
				size = 26,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			enabled = false,
			externals = {
				anchorPoint = "CENTER",
				anchorOutside = false,
				borderColor = { 1, 0.25, 0.25, 1 },
				borderOffset = 0,
				borderSize = nil,
				borderTexture = "DEFAULT",
				cooldownAnchor = "CENTER",
				cooldownFontOutline = "OUTLINE",
				cooldownFontSize = 12,
				cooldownOffset = {
					x = 0,
					y = 0,
				},
				countAnchor = "BOTTOMRIGHT",
				countFontOutline = "OUTLINE",
				countFontSize = 12,
				countOffset = {
					x = 4,
					y = 2,
				},
				drAnchor = "TOPLEFT",
				drColor = {
					1,
					1,
					1,
					1,
				},
				drFontOutline = "OUTLINE",
				drFontSize = 10,
				drOffset = {
					x = 2,
					y = -2,
				},
				enabled = false,
				glowColor = { 1, 0.25, 0.25, 1 },
				glowEnabled = false,
				glowInset = 0,
				glowStyle = "MARCHING_ANTS",
				growth = "RIGHTDOWN",
				growthX = "RIGHT",
				growthY = "DOWN",
				max = 2,
				perRow = 6,
				showCooldown = true,
				showCooldownText = true,
				showDR = false,
				showStacks = false,
				showTooltip = false,
				size = 34,
				spacing = 0,
				textFont = "Friz Quadrata TT",
				textOutline = "OUTLINE",
				x = 0,
				y = 0,
			},
			provider = "HARF",
			useAdvancedRaidFrames = true,
		},
		barTexture = "SOLID",
		border = {
			color = {
				0,
				0,
				0,
				0.8,
			},
			edgeSize = 1,
			enabled = false,
			frameLevelOffset = 3,
			inset = 0,
			offset = 0,
			strata = "",
			texture = "DEFAULT",
		},
		columnSpacing = 8,
		customSort = {
			classOrder = {
				"DEATHKNIGHT",
				"DEMONHUNTER",
				"DRUID",
				"EVOKER",
				"HUNTER",
				"MAGE",
				"MONK",
				"PALADIN",
				"PRIEST",
				"ROGUE",
				"SHAMAN",
				"WARLOCK",
				"WARRIOR",
			},
			enabled = false,
			playerFirstInRole = false,
			roleOrder = {
				"TANK",
				"HEALER",
				"DAMAGER",
			},
			separateMeleeRanged = false,
		},
		dynamicContentScale = false,
		dynamicScaleMin = 0,
		enabled = false,
		groupGrowth = "DOWN",
		groupIndicator = {
			anchor = "TOPLEFT",
			color = {
				1,
				1,
				1,
				1,
			},
			enabled = false,
			fontOutline = "OUTLINE",
			fontSize = 12,
			format = "GROUP",
			hidePerFrame = false,
			offset = {
				x = 0,
				y = 0,
			},
		},
		growth = "DOWN",
		healerBuffPlacement = {
			_eqolNormalized = true,
			enabled = false,
			groupOrder = {},
			groupsById = {},
			ruleOrder = {},
			rulesById = {},
			version = 1,
		},
		health = {
			absorbColor = {
				0.85,
				0.95,
				1,
				0.7,
			},
			absorbEnabled = true,
			absorbDontOverflowHealthBar = false,
			absorbReverseFill = false,
			absorbTexture = "SOLID",
			absorbUseCustomColor = false,
			useAbsorbGlow = false,
			backdrop = {
				clampToFill = false,
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			color = {
				0,
				0.8,
				0,
				1,
			},
			fontOutline = "OUTLINE",
			fontSize = 12,
			healAbsorbColor = {
				1,
				0.3,
				0.3,
				0.7,
			},
			healAbsorbEnabled = true,
			healAbsorbDontOverflowHealthBar = true,
			healAbsorbReverseFill = true,
			healAbsorbTexture = "SOLID",
			healAbsorbUseCustomColor = false,
			hidePercentSymbol = false,
			incomingHealColor = {
				0.2,
				0.85,
				0.35,
				0.45,
			},
			incomingHealEnabled = false,
			offsetCenter = {
				x = 0,
				y = 0,
			},
			offsetLeft = {
				x = 5,
				y = 0,
			},
			offsetRight = {
				x = -5,
				y = 0,
			},
			showSampleAbsorb = false,
			showSampleHealAbsorb = false,
			showSampleIncomingHeal = false,
			tempMaxHealthLossEnabled = true,
			textCenter = "PERCENT",
			textColor = {
				1,
				1,
				1,
				1,
			},
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "CURRENT",
			texture = "DEFAULT",
			smoothFill = false,
			useClassColor = true,
			useCustomColor = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		height = 100,
		hideInClientScene = true,
		highlight = {
			aggro = false,
			color = {
				1,
				0,
				0,
				1,
			},
			enabled = false,
			mouseover = true,
			size = 2,
			texture = "DEFAULT",
		},
		highlightAggro = {
			color = {
				1,
				0,
				0,
				1,
			},
			enabled = false,
			layer = "ABOVE_BORDER",
			mode = "ALL",
			offset = 0,
			sample = false,
			size = 2,
			texture = "DEFAULT",
		},
		highlightHover = {
			color = {
				1,
				1,
				1,
				0.9,
			},
			enabled = false,
			offset = 0,
			size = 2,
			strata = nil,
			texture = "DEFAULT",
		},
		highlightTarget = {
			color = {
				1,
				1,
				0,
				1,
			},
			enabled = false,
			layer = "ABOVE_BORDER",
			offset = 0,
			size = 2,
			texture = "DEFAULT",
		},
		maxColumns = 1,
		point = "TOPLEFT",
		portrait = {
			borderWithFrame = false,
			enabled = false,
			mode = "PORTRAIT",
			separator = {
				enabled = true,
				texture = "SOLID",
			},
			side = "LEFT",
			squareBackground = false,
		},
		power = {
			backdrop = {
				color = {
					0,
					0,
					0,
					0.6,
				},
				enabled = true,
				texture = "DEFAULT",
			},
			fontOutline = "OUTLINE",
			fontSize = 9,
			hidePercentSymbol = false,
			offsetCenter = {
				x = 0,
				y = 0,
			},
			offsetLeft = {
				x = 5,
				y = 0,
			},
			offsetRight = {
				x = -5,
				y = 0,
			},
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			showSpecs = {},
			textCenter = "NONE",
			textDelimiter = " ",
			textDelimiterSecondary = " ",
			textDelimiterTertiary = " ",
			textLeft = "NONE",
			textRight = "NONE",
			texture = "DEFAULT",
			smoothFill = false,
			useShortNumbers = true,
			roundPercent = false,
		},
		powerHeight = 6,
		privateAuras = {
			countdownFrame = true,
			countdownNumbers = false,
			showTooltip = false,
			duration = {
				enable = false,
				offsetX = 0,
				offsetY = -1,
				point = "BOTTOM",
			},
			enabled = false,
			icon = {
				amount = 2,
				offset = 2,
				point = "LEFT",
				size = 18,
			},
			parent = {
				offsetX = 0,
				offsetY = 0,
				point = "CENTER",
			},
			showDispelType = false,
		},
		relativePoint = "TOPLEFT",
		relativeTo = "UIParent",
		roleIcon = {
			enabled = true,
			point = "TOPLEFT",
			relativePoint = "TOPLEFT",
			showRoles = {
				DAMAGER = false,
				HEALER = true,
				TANK = true,
			},
			size = 16,
			spacing = 2,
			style = "TINY",
			x = 2,
			y = -2,
		},
		sortDir = "ASC",
		sortMethod = "NAME",
		spacing = 1,
		status = {
			assistIcon = {
				enabled = true,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				size = 10,
				x = 14,
				y = -1,
			},
			dispelTint = {
				alpha = 0.25,
				enabled = true,
				fillAlpha = 0.2,
				fillColor = {
					0,
					0,
					0,
					1,
				},
				fillEnabled = true,
				glowColor = {
					1,
					1,
					1,
					1,
				},
				glowColorMode = "DISPEL",
				glowEffect = "PIXEL",
				glowEnabled = false,
				glowFrequency = 0.25,
				glowLines = 8,
				glowOverOverlay = false,
				glowThickness = 3,
				glowX = 0,
				glowY = 0,
				showSample = false,
			},
			groupNumber = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = false,
				fontOutline = "OUTLINE",
				fontSize = 12,
				format = "GROUP",
				offset = {
					x = 0,
					y = 0,
				},
			},
			hideLevelAtMax = true,
			leaderIcon = {
				enabled = true,
				point = "TOPRIGHT",
				relativePoint = "TOPRIGHT",
				size = 19,
				x = 6,
				y = 10,
			},
			levelAnchor = "TOPRIGHT",
			levelColor = {
				1,
				0.85,
				0,
				1,
			},
			levelColorMode = "CUSTOM",
			levelEnabled = true,
			levelFontOutline = "OUTLINE",
			levelFontSize = 12,
			levelOffset = {
				x = -6,
				y = -4,
			},
			nameColor = {
				1,
				1,
				1,
				1,
			},
			nameColorMode = "CLASS",
			phaseIcon = {
				enabled = false,
				point = "TOPLEFT",
				relativePoint = "TOPLEFT",
				sample = false,
				size = 14,
				x = 0,
				y = 0,
			},
			raidIcon = {
				enabled = true,
				point = "TOP",
				relativePoint = "TOP",
				size = 18,
				x = 0,
				y = 12,
			},
			rangeFade = {
				alpha = 0.55,
				enabled = true,
				offlineAlpha = 0.4,
			},
			readyCheckIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			resurrectIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			summonIcon = {
				enabled = true,
				point = "CENTER",
				relativePoint = "CENTER",
				sample = false,
				size = 16,
				x = 0,
				y = 0,
			},
			unitStatus = {
				anchor = "CENTER",
				color = {
					1,
					1,
					1,
					1,
				},
				enabled = true,
				fontOutline = "OUTLINE",
				fontSize = 12,
				groupFormat = "GROUP",
				hideHealthTextWhenOffline = false,
				offset = {
					x = 0,
					y = 0,
				},
				showAFK = false,
				showDead = true,
				showDND = false,
				showGhost = true,
				showGroup = false,
				showOffline = true,
			},
		},
		text = {
			fontOutline = "OUTLINE",
			fontSize = 15,
			nameAnchor = "TOP",
			nameMaxChars = 15,
			nameNoEllipsis = false,
			nameOffset = {
				x = 0,
				y = -4,
			},
			showHealthPercent = false,
			showName = true,
			showPowerPercent = false,
			useClassColor = true,
		},
		tooltip = {
			mode = "OFF",
			modifier = "ALT",
			useEditMode = false,
		},
		unitsPerColumn = 2,
		width = 180,
		x = 700,
		y = -120,
	},
}

local function copyDefaultsTable(src)
	if type(src) ~= "table" then return src end
	if addon.functions and addon.functions.copyTable then return addon.functions.copyTable(src) end
	if CopyTable then return CopyTable(src) end
	local out = {}
	for k, v in pairs(src) do
		if type(v) == "table" then
			out[k] = copyDefaultsTable(v)
		else
			out[k] = v
		end
	end
	return out
end

do
	local mtDefaults = copyDefaultsTable(DEFAULTS.raid)
	mtDefaults.enabled = false
	mtDefaults.sortMethod = "NAME"
	mtDefaults.sortDir = "ASC"
	mtDefaults.groupBy = nil
	mtDefaults.groupingOrder = nil
	mtDefaults.groupFilter = nil
	mtDefaults.viewerRoleFilter = "ALL"
	mtDefaults.unitsPerColumn = 2
	mtDefaults.maxColumns = 1
	mtDefaults.growth = "DOWN"
	mtDefaults.point = "TOPLEFT"
	mtDefaults.relativePoint = "TOPLEFT"
	mtDefaults.relativeTo = "UIParent"
	mtDefaults.x = 500
	mtDefaults.y = -120
	DEFAULTS.mt = mtDefaults

	local maDefaults = copyDefaultsTable(DEFAULTS.raid)
	maDefaults.enabled = false
	maDefaults.sortMethod = "NAME"
	maDefaults.sortDir = "ASC"
	maDefaults.groupBy = nil
	maDefaults.groupingOrder = nil
	maDefaults.groupFilter = nil
	maDefaults.viewerRoleFilter = "ALL"
	maDefaults.unitsPerColumn = 2
	maDefaults.maxColumns = 1
	maDefaults.growth = "DOWN"
	maDefaults.point = "TOPLEFT"
	maDefaults.relativePoint = "TOPLEFT"
	maDefaults.relativeTo = "UIParent"
	maDefaults.x = 700
	maDefaults.y = -120
	DEFAULTS.ma = maDefaults
end

local DB

local function sanitizeHealthColorMode(cfg)
	local hc = cfg and cfg.health
	if not hc then return end
	if hc.useCustomColor then
		hc.useClassColor = false
	elseif hc.useClassColor then
		hc.useCustomColor = false
	end
end

function GF._hbpAppendUniqueOrderId(order, id)
	if type(order) ~= "table" or id == nil then return end
	id = tostring(id)
	for i = 1, #order do
		if tostring(order[i]) == id then return end
	end
	order[#order + 1] = id
end

function GF._hbpNextNumericMapId(map)
	local maxId = 0
	for id in pairs(map or EMPTY) do
		local n = tonumber(id)
		if n and n > maxId then maxId = n end
	end
	return tostring(maxId + 1)
end

function GF._hbpHasPlacementData(placement)
	if type(placement) ~= "table" then return false end
	if placement.enabled == true then return true end
	local groups = placement.groupsById
	if type(groups) == "table" and next(groups) then return true end
	local rules = placement.rulesById
	if type(rules) == "table" and next(rules) then return true end
	return false
end

function GF._hbpCollectOrderedIds(order, map)
	local out, seen = {}, {}
	if type(order) == "table" then
		for i = 1, #order do
			local id = order[i]
			if id ~= nil then
				id = tostring(id)
				if map and map[id] and not seen[id] then
					seen[id] = true
					out[#out + 1] = id
				end
			end
		end
	end
	local missing = {}
	for id in pairs(map or EMPTY) do
		local normalized = tostring(id)
		if not seen[normalized] then missing[#missing + 1] = normalized end
	end
	table.sort(missing, function(a, b)
		local na = tonumber(a)
		local nb = tonumber(b)
		if na and nb then return na < nb end
		if na then return true end
		if nb then return false end
		return tostring(a) < tostring(b)
	end)
	for i = 1, #missing do
		out[#out + 1] = missing[i]
	end
	return out
end

function GF._hbpPlacementDigest(placement)
	if type(placement) ~= "table" then return "" end
	local parts = {}
	local groups = type(placement.groupsById) == "table" and placement.groupsById or EMPTY
	local rules = type(placement.rulesById) == "table" and placement.rulesById or EMPTY
	local groupOrder = GF._hbpCollectOrderedIds(placement.groupOrder, groups)
	local ruleOrder = GF._hbpCollectOrderedIds(placement.ruleOrder, rules)

	parts[#parts + 1] = placement.enabled == true and "e:1" or "e:0"
	parts[#parts + 1] = "v:" .. tostring(placement.version or "")
	parts[#parts + 1] = "go:" .. table.concat(groupOrder, ",")
	for i = 1, #groupOrder do
		local groupId = groupOrder[i]
		local group = groups[groupId]
		if type(group) == "table" then
			local color = group.color
			local cr = tonumber(color and (color[1] or color.r) or 0) or 0
			local cg = tonumber(color and (color[2] or color.g) or 0) or 0
			local cb = tonumber(color and (color[3] or color.b) or 0) or 0
			local ca = tonumber(color and (color[4] or color.a) or 0) or 0
			parts[#parts + 1] = table.concat({
				"g",
				tostring(groupId),
				tostring(group.id or ""),
				tostring(group.name or ""),
				tostring(group.style or ""),
				tostring(group.anchorPoint or ""),
				tostring(group.x or ""),
				tostring(group.y or ""),
				tostring(group.growth or ""),
				tostring(group.perRow or ""),
				tostring(group.max or ""),
				tostring(group.spacing or ""),
				tostring(group.size or ""),
				tostring(group.barOrientation or ""),
				tostring(group.barThickness or ""),
				tostring(group.inset or ""),
				tostring(group.borderSize or ""),
				tostring(group.borderStrata or ""),
				tostring(group.borderFrameLevelOffset or ""),
				tostring(group.ruleMatch or ""),
				tostring(group.iconMode or ""),
				tostring(cr),
				tostring(cg),
				tostring(cb),
				tostring(ca),
			}, "|")
		else
			parts[#parts + 1] = "g|" .. tostring(groupId) .. "|nil"
		end
	end

	parts[#parts + 1] = "ro:" .. table.concat(ruleOrder, ",")
	for i = 1, #ruleOrder do
		local ruleId = ruleOrder[i]
		local rule = rules[ruleId]
		if type(rule) == "table" then
			parts[#parts + 1] = table.concat({
				"r",
				tostring(ruleId),
				tostring(rule.id or ""),
				tostring(rule.spellFamilyId or ""),
				tostring(rule.groupId or ""),
				rule.enabled == false and "0" or "1",
				rule["not"] == true and "1" or "0",
				rule.appliesParty == false and "0" or "1",
				rule.appliesRaid == false and "0" or "1",
			}, "|")
		else
			parts[#parts + 1] = "r|" .. tostring(ruleId) .. "|nil"
		end
	end

	return table.concat(parts, ";")
end

function GF._hbpPlacementScore(placement)
	if type(placement) ~= "table" then return 0 end
	local groups = type(placement.groupsById) == "table" and placement.groupsById or EMPTY
	local rules = type(placement.rulesById) == "table" and placement.rulesById or EMPTY
	local groupCount = 0
	local ruleCount = 0
	for _ in pairs(groups) do
		groupCount = groupCount + 1
	end
	for _ in pairs(rules) do
		ruleCount = ruleCount + 1
	end
	local score = (groupCount * 1000) + ruleCount
	if placement.enabled == true then score = score + 1 end
	return score
end

function GF._hbpRuleSignature(rule, includeGroupId, groupIdOverride)
	if type(rule) ~= "table" then return "" end
	local groupPart = ""
	if includeGroupId ~= false then groupPart = tostring(groupIdOverride or rule.groupId or "") end
	return table.concat({
		groupPart,
		tostring(rule.spellFamilyId or ""),
		rule.enabled == false and "0" or "1",
		rule["not"] == true and "1" or "0",
		rule.appliesParty == false and "0" or "1",
		rule.appliesRaid == false and "0" or "1",
	}, "|")
end

function GF._hbpGroupSignature(group, ruleBlob)
	if type(group) ~= "table" then return "" end
	local color = group.color
	local cr = tonumber(color and (color[1] or color.r) or 0) or 0
	local cg = tonumber(color and (color[2] or color.g) or 0) or 0
	local cb = tonumber(color and (color[3] or color.b) or 0) or 0
	local ca = tonumber(color and (color[4] or color.a) or 0) or 0
	return table.concat({
		tostring(group.name or ""),
		tostring(group.style or ""),
		tostring(group.anchorPoint or ""),
		tostring(group.x or ""),
		tostring(group.y or ""),
		tostring(group.growth or ""),
		tostring(group.perRow or ""),
		tostring(group.max or ""),
		tostring(group.spacing or ""),
		tostring(group.size or ""),
		tostring(group.barOrientation or ""),
		tostring(group.barThickness or ""),
		tostring(group.inset or ""),
		tostring(group.borderSize or ""),
		tostring(group.borderStrata or ""),
		tostring(group.borderFrameLevelOffset or ""),
		tostring(group.ruleMatch or ""),
		tostring(group.iconMode or ""),
		tostring(cr),
		tostring(cg),
		tostring(cb),
		tostring(ca),
		tostring(ruleBlob or ""),
	}, "|")
end

function GF._hbpDeduplicatePlacement(placement)
	if type(placement) ~= "table" then return 0, 0 end
	placement.groupsById = type(placement.groupsById) == "table" and placement.groupsById or {}
	placement.groupOrder = type(placement.groupOrder) == "table" and placement.groupOrder or {}
	placement.rulesById = type(placement.rulesById) == "table" and placement.rulesById or {}
	placement.ruleOrder = type(placement.ruleOrder) == "table" and placement.ruleOrder or {}

	local groups = placement.groupsById
	local rules = placement.rulesById
	local groupOrder = GF._hbpCollectOrderedIds(placement.groupOrder, groups)
	local ruleOrder = GF._hbpCollectOrderedIds(placement.ruleOrder, rules)
	local rulesByGroup = {}
	for i = 1, #ruleOrder do
		local ruleId = ruleOrder[i]
		local rule = rules[ruleId]
		if type(rule) == "table" then
			local groupId = tostring(rule.groupId or "")
			if groups[groupId] then
				local list = rulesByGroup[groupId]
				if not list then
					list = {}
					rulesByGroup[groupId] = list
				end
				list[#list + 1] = ruleId
			end
		end
	end

	local groupSigToKeepId = {}
	local groupRemap = {}
	local keptGroups = {}
	local keptGroupOrder = {}
	local removedGroups = 0
	for i = 1, #groupOrder do
		local groupId = groupOrder[i]
		local group = groups[groupId]
		if type(group) == "table" then
			local ruleSigParts = {}
			local list = rulesByGroup[groupId]
			if list then
				for j = 1, #list do
					local rule = rules[list[j]]
					ruleSigParts[#ruleSigParts + 1] = GF._hbpRuleSignature(rule, false)
				end
			end
			table.sort(ruleSigParts)
			local groupSig = GF._hbpGroupSignature(group, table.concat(ruleSigParts, ","))
			local keepId = groupSigToKeepId[groupSig]
			if keepId then
				groupRemap[groupId] = keepId
				removedGroups = removedGroups + 1
			else
				groupSigToKeepId[groupSig] = groupId
				groupRemap[groupId] = groupId
				keptGroups[groupId] = group
				keptGroupOrder[#keptGroupOrder + 1] = groupId
			end
		else
			removedGroups = removedGroups + 1
		end
	end

	local keptRules = {}
	local keptRuleOrder = {}
	local seenRuleSigs = {}
	local removedRules = 0
	for i = 1, #ruleOrder do
		local ruleId = ruleOrder[i]
		local rule = rules[ruleId]
		if type(rule) == "table" then
			local oldGroupId = tostring(rule.groupId or "")
			local newGroupId = groupRemap[oldGroupId]
			if newGroupId and keptGroups[newGroupId] then
				rule.groupId = newGroupId
				local sig = GF._hbpRuleSignature(rule, true, newGroupId)
				if not seenRuleSigs[sig] then
					seenRuleSigs[sig] = true
					keptRules[ruleId] = rule
					keptRuleOrder[#keptRuleOrder + 1] = ruleId
				else
					removedRules = removedRules + 1
				end
			else
				removedRules = removedRules + 1
			end
		else
			removedRules = removedRules + 1
		end
	end

	placement.groupsById = keptGroups
	placement.groupOrder = keptGroupOrder
	placement.rulesById = keptRules
	placement.ruleOrder = keptRuleOrder
	return removedGroups, removedRules
end

function GF._hbpMergePlacement(basePlacement, incomingPlacement)
	if type(basePlacement) ~= "table" or type(incomingPlacement) ~= "table" then return end
	if basePlacement == incomingPlacement then return end

	basePlacement.groupsById = type(basePlacement.groupsById) == "table" and basePlacement.groupsById or {}
	basePlacement.groupOrder = type(basePlacement.groupOrder) == "table" and basePlacement.groupOrder or {}
	basePlacement.rulesById = type(basePlacement.rulesById) == "table" and basePlacement.rulesById or {}
	basePlacement.ruleOrder = type(basePlacement.ruleOrder) == "table" and basePlacement.ruleOrder or {}

	local inGroups = type(incomingPlacement.groupsById) == "table" and incomingPlacement.groupsById or EMPTY
	local inRules = type(incomingPlacement.rulesById) == "table" and incomingPlacement.rulesById or EMPTY
	local groupRemap = {}

	local incomingGroupIds = GF._hbpCollectOrderedIds(incomingPlacement.groupOrder, inGroups)
	for i = 1, #incomingGroupIds do
		local sourceId = incomingGroupIds[i]
		local sourceGroup = inGroups[sourceId]
		if sourceGroup then
			local targetId = sourceId
			if basePlacement.groupsById[targetId] then targetId = GF._hbpNextNumericMapId(basePlacement.groupsById) end
			local groupCopy = copyDefaultsTable(sourceGroup)
			groupCopy.id = targetId
			basePlacement.groupsById[targetId] = groupCopy
			GF._hbpAppendUniqueOrderId(basePlacement.groupOrder, targetId)
			groupRemap[sourceId] = targetId
		end
	end

	local incomingRuleIds = GF._hbpCollectOrderedIds(incomingPlacement.ruleOrder, inRules)
	for i = 1, #incomingRuleIds do
		local sourceId = incomingRuleIds[i]
		local sourceRule = inRules[sourceId]
		if sourceRule then
			local sourceGroupId = tostring(sourceRule.groupId or "")
			local targetGroupId = groupRemap[sourceGroupId] or (basePlacement.groupsById[sourceGroupId] and sourceGroupId) or nil
			if targetGroupId then
				local targetId = sourceId
				if basePlacement.rulesById[targetId] then targetId = GF._hbpNextNumericMapId(basePlacement.rulesById) end
				local ruleCopy = copyDefaultsTable(sourceRule)
				ruleCopy.id = targetId
				ruleCopy.groupId = targetGroupId
				basePlacement.rulesById[targetId] = ruleCopy
				GF._hbpAppendUniqueOrderId(basePlacement.ruleOrder, targetId)
			end
		end
	end

	if incomingPlacement.enabled == true then basePlacement.enabled = true end
end

function GF._ensureSharedHealerBuffPlacement(db)
	if type(db) ~= "table" then return nil end
	local partyCfg = type(db.party) == "table" and db.party or nil
	local raidCfg = type(db.raid) == "table" and db.raid or nil
	if not (partyCfg and raidCfg) then return nil end
	local partyPlacement = partyCfg.healerBuffPlacement
	local raidPlacement = raidCfg.healerBuffPlacement
	if type(partyPlacement) == "table" and partyPlacement == raidPlacement then
		partyPlacement._eqolUnified = true
		return partyPlacement
	end

	local hbm = UF.GroupFramesHealerBuffs
	if hbm and hbm.EnsureConfig then
		hbm.EnsureConfig(partyCfg)
		hbm.EnsureConfig(raidCfg)
	else
		if type(partyCfg.healerBuffPlacement) ~= "table" then partyCfg.healerBuffPlacement = GF._createHealerBuffPlacementDefaults() end
		if type(raidCfg.healerBuffPlacement) ~= "table" then raidCfg.healerBuffPlacement = GF._createHealerBuffPlacementDefaults() end
	end

	partyPlacement = partyCfg.healerBuffPlacement
	raidPlacement = raidCfg.healerBuffPlacement
	if type(partyPlacement) ~= "table" then
		partyPlacement = GF._createHealerBuffPlacementDefaults()
		partyCfg.healerBuffPlacement = partyPlacement
	end
	if type(raidPlacement) ~= "table" then
		raidPlacement = GF._createHealerBuffPlacementDefaults()
		raidCfg.healerBuffPlacement = raidPlacement
	end

	GF._hbpDeduplicatePlacement(partyPlacement)
	GF._hbpDeduplicatePlacement(raidPlacement)

	if partyPlacement ~= raidPlacement then
		local partyHasData = GF._hbpHasPlacementData(partyPlacement)
		local raidHasData = GF._hbpHasPlacementData(raidPlacement)
		local shared = partyPlacement
		if not partyHasData and raidHasData then
			shared = raidPlacement
			GF._hbpMergePlacement(shared, partyPlacement)
		elseif partyHasData and raidHasData then
			local partyDigest = GF._hbpPlacementDigest(partyPlacement)
			local raidDigest = GF._hbpPlacementDigest(raidPlacement)
			if partyDigest ~= raidDigest then
				local alreadyMerged = partyPlacement._eqolMergedFromDual == true or raidPlacement._eqolMergedFromDual == true
				if alreadyMerged then
					if GF._hbpPlacementScore(raidPlacement) > GF._hbpPlacementScore(partyPlacement) then shared = raidPlacement end
				else
					GF._hbpMergePlacement(shared, raidPlacement)
					shared._eqolMergedFromDual = true
					GF._hbpDeduplicatePlacement(shared)
				end
			end
		end
		if type(shared) == "table" and (partyPlacement._eqolMergedFromDual == true or raidPlacement._eqolMergedFromDual == true) then shared._eqolMergedFromDual = true end
		GF._hbpDeduplicatePlacement(shared)
		partyCfg.healerBuffPlacement = shared
		raidCfg.healerBuffPlacement = shared
	end
	if type(partyCfg.healerBuffPlacement) == "table" then partyCfg.healerBuffPlacement._eqolUnified = true end
	if type(raidCfg.healerBuffPlacement) == "table" then raidCfg.healerBuffPlacement._eqolUnified = true end

	if hbm and hbm.EnsureConfig then
		hbm.EnsureConfig(partyCfg)
		hbm.EnsureConfig(raidCfg)
	end

	return partyCfg.healerBuffPlacement
end

local function ensureDB()
	if UF and UF.Profiles and UF.Profiles.MaybeInitialize then
		UF.Profiles.MaybeInitialize()
	elseif UF and UF.Profiles and UF.Profiles.Initialize then
		UF.Profiles.Initialize()
	end
	addon.db = addon.db or {}
	addon.db.ufGroupFrames = addon.db.ufGroupFrames or {}
	local db = addon.db.ufGroupFrames
	-- Always merge defaults so newly introduced kinds/fields are backfilled
	-- for existing profiles that already have _eqolInited = true.
	for kind, def in pairs(DEFAULTS) do
		db[kind] = db[kind] or {}
		local t = db[kind]
		for k, v in pairs(def) do
			if t[k] == nil then
				if type(v) == "table" then
					if addon.functions and addon.functions.copyTable then
						t[k] = addon.functions.copyTable(v)
					else
						t[k] = CopyTable(v)
					end
				else
					t[k] = v
				end
			end
		end
		sanitizeHealthColorMode(t)
		do
			local healthCfg = t.health or {}
			t.health = healthCfg
			local defHealth = (def and def.health) or {}
			local overlayFallback = GF._computeOverlayHeightFallback((t.height ~= nil and t.height) or def.height, (t.powerHeight ~= nil and t.powerHeight) or def.powerHeight)
			healthCfg.absorbOverlayHeight = GF._resolveOverlayHeightSetting(healthCfg.absorbOverlayHeight ~= nil and healthCfg.absorbOverlayHeight or defHealth.absorbOverlayHeight, overlayFallback)
			healthCfg.healAbsorbOverlayHeight =
				GF._resolveOverlayHeightSetting(healthCfg.healAbsorbOverlayHeight ~= nil and healthCfg.healAbsorbOverlayHeight or defHealth.healAbsorbOverlayHeight, overlayFallback)
		end
		if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.EnsureConfig then UF.GroupFramesHealerBuffs.EnsureConfig(t) end
		if kind == "party" then
			-- Legacy party defaults grouped by role; clear persisted values so INDEX uses party unit index order.
			t.groupBy = nil
			t.groupingOrder = nil
		end
	end
	GF._ensureSharedHealerBuffPlacement(db)
	db._eqolInited = true
	if DB ~= db and UF and UF.Profiles and UF.Profiles.Debug then
		local partyEnabled = db.party and db.party.enabled == true
		local raidEnabled = db.raid and db.raid.enabled == true
		UF.Profiles.Debug("GF ensureDB cache %s -> %s (party=%s, raid=%s)", tostring(DB), tostring(db), tostring(partyEnabled), tostring(raidEnabled))
		if UF.Profiles.Trace then UF.Profiles.Trace("GF_ENSURE_DB", "CACHE_SWAP") end
	end
	DB = db
	return db
end

getCfg = function(kind)
	local db = DB or ensureDB()
	if db[kind] == nil then
		local def = DEFAULTS[kind]
		if type(def) == "table" then
			if addon.functions and addon.functions.copyTable then
				db[kind] = addon.functions.copyTable(def)
			else
				db[kind] = CopyTable(def)
			end
		else
			return def
		end
	end
	GF._ensureSharedHealerBuffPlacement(db)
	return db[kind]
end

local function isFeatureEnabled()
	local db = DB or ensureDB()
	local partyEnabled = db and db.party and db.party.enabled == true
	local raidEnabled = db and db.raid and db.raid.enabled == true
	return partyEnabled or raidEnabled
end

local hiddenParent
local function getHiddenParent()
	if hiddenParent then return hiddenParent end
	hiddenParent = CreateFrame("Frame")
	hiddenParent:Hide()
	return hiddenParent
end

local function hideFrameLocked(frame)
	if not frame or frame._eqolHidden then return end

	local function enforceHidden(target)
		if not target then return end
		local canHide = true
		if InCombatLockdown and InCombatLockdown() then
			if target.IsProtected and target:IsProtected() then canHide = false end
		end
		if canHide then
			if target.Hide then pcall(target.Hide, target) end
		elseif target.SetAlpha then
			target:SetAlpha(0)
			target._eqolAlphaHidden = true
		end
	end

	if frame.UnregisterAllEvents then frame:UnregisterAllEvents() end
	enforceHidden(frame)
	frame._eqolHidden = true
	if frame.SetParent then pcall(frame.SetParent, frame, getHiddenParent()) end
	if not frame._eqolHiddenHooks then
		frame._eqolHiddenHooks = true
		if frame.Show then hooksecurefunc(frame, "Show", function(f) enforceHidden(f) end) end
		if frame.SetShown then hooksecurefunc(frame, "SetShown", function(f, shown)
			if shown then enforceHidden(f) end
		end) end
	end
end

function GF:DisableBlizzardFrames()
	local db = DB or ensureDB()
	local wantParty = db and db.party and db.party.enabled == true
	local wantRaid = db and db.raid and db.raid.enabled == true
	if InCombatLockdown and InCombatLockdown() then
		GF._pendingBlizzardDisable = { party = wantParty, raid = wantRaid }
		return
	end
	local pending = GF._pendingBlizzardDisable or {}
	GF._pendingBlizzardDisable = nil
	if wantParty or pending.party then
		hideFrameLocked(_G.PartyFrame)
		hideFrameLocked(_G.CompactPartyFrame)
		hideFrameLocked(_G.CompactPartyFrameTitle)
	end
	if wantRaid or pending.raid then
		hideFrameLocked(_G.CompactRaidFrameContainer)
		if CompactRaidFrameManager_SetSetting then pcall(CompactRaidFrameManager_SetSetting, "IsShown", "0") end
	end
end

function GF:GetConfig(kind) return getCfg(kind) end

function GF:GetHealerBuffPlacementConfig(kind)
	kind = tostring(kind or "party"):lower()
	if kind == "mt" or kind == "ma" then kind = "raid" end
	if kind ~= "party" and kind ~= "raid" then kind = "party" end
	local db = DB or ensureDB()
	GF._ensureSharedHealerBuffPlacement(db)
	return getCfg(kind)
end

function GF:IsFeatureEnabled() return isFeatureEnabled() end

function GF:EnsureDB() return ensureDB() end

function GF:ResetDBCache()
	DB = nil
	return ensureDB()
end

function GF:ApplyProfileChange(reason)
	self._lastProfileChangeReason = reason or self._lastProfileChangeReason
	if UF and UF.Profiles and UF.Profiles.Debug then UF.Profiles.Debug("GF apply profile change (reason=%s)", tostring(reason)) end
	if UF and UF.Profiles and UF.Profiles.Trace then UF.Profiles.Trace("GF_APPLY_PROFILE_CHANGE", reason) end
	self:ResetDBCache()
	for _, header in pairs(self.headers or {}) do
		if header then header._eqolLastSecureLayoutKey = nil end
	end
	if not isFeatureEnabled() then
		self:DisableFeature()
		return
	end
	self:EnableFeature()
	if InCombatLockdown and InCombatLockdown() then
		self._pendingRefresh = true
	else
		self:RunProfileChangeRefreshPass()
	end
end

GF.headers = GF.headers or {}
GF.anchors = GF.anchors or {}
GF._pendingRefresh = GF._pendingRefresh or false
GF._pendingHeaderKinds = GF._pendingHeaderKinds or {}
GF._pendingSortKinds = GF._pendingSortKinds or {}
GF._lightHeaderRefreshOptions = GF._lightHeaderRefreshOptions or { skipChildSync = true }
GF._pendingDisable = GF._pendingDisable or false
GF._clientSceneActive = GF._clientSceneActive or false
GF._postRosterHeaderApplyPending = GF._postRosterHeaderApplyPending or false

local registerFeatureEvents
local unregisterFeatureEvents

function GF:MarkPendingHeaderRefresh(kind)
	GF._pendingRefresh = true
	GF._pendingHeaderKinds = GF._pendingHeaderKinds or {}
	if kind then GF._pendingHeaderKinds[kind] = true end
end

function GF:MarkPendingSortRefresh(kind)
	GF._pendingSortKinds = GF._pendingSortKinds or {}
	if kind then GF._pendingSortKinds[kind] = true end
end

function GF:SetHeaderAttributeIfChanged(header, key, value)
	if not (header and header.SetAttribute) then return false end
	local cache = header._eqolAttrCache
	if not cache then
		cache = {}
		header._eqolAttrCache = cache
	end
	local normalized = value
	if normalized == nil then
		GF._eqolAttrNilToken = GF._eqolAttrNilToken or {}
		normalized = GF._eqolAttrNilToken
	end
	if cache[key] == normalized then return false end
	header:SetAttribute(key, value)
	cache[key] = normalized
	return true
end

function GF:ApplyPendingHeaderKinds()
	local pending = GF._pendingHeaderKinds
	if not pending then return false end
	GF._pendingHeaderKinds = {}
	local applied = false

	if pending.party then
		GF:ApplyHeaderAttributes("party")
		applied = true
	end
	if pending.raid then
		GF:ApplyHeaderAttributes("raid")
		applied = true
	end
	if pending.mt then
		GF:ApplyHeaderAttributes("mt")
		applied = true
	end
	if pending.ma then
		GF:ApplyHeaderAttributes("ma")
		applied = true
	end

	for kind in pairs(pending) do
		if kind ~= "party" and kind ~= "raid" and kind ~= "mt" and kind ~= "ma" then
			GF:ApplyHeaderAttributes(kind)
			applied = true
		end
	end

	return applied
end

function GF:ApplyPendingSortKinds()
	local pending = GF._pendingSortKinds
	if not pending or not next(pending) then return false end
	GF._pendingSortKinds = {}
	local applied = false

	for kind in pairs(pending) do
		if kind == "party" or kind == "raid" then
			GF:RefreshCustomSortNameList(kind)
			applied = true
		end
	end

	return applied
end

local function getUnit(self)
	if not self then return nil end

	if self._eqolUseSecureUnitAttribute and self.GetAttribute then
		local secureUnit = self:GetAttribute("unit")
		if secureUnit == "" then secureUnit = nil end
		return secureUnit
	end

	local secureUnit = self.GetAttribute and self:GetAttribute("unit")
	if secureUnit ~= nil and secureUnit ~= "" then return secureUnit end

	return self.unit
end

local function unitTokenExists(unit)
	if unit == nil or unit == "" then return false end
	local exists = UnitExists and UnitExists(unit)
	if issecretvalue and issecretvalue(exists) then exists = nil end
	if exists then return true end
	local visible = UnitIsVisible and UnitIsVisible(unit)
	if issecretvalue and issecretvalue(visible) then visible = nil end
	return visible == true
end

local function getState(self)
	local st = self and self._eqolUFState
	if not st then
		st = { frame = self }
		self._eqolUFState = st
	end
	return st
end

local function isTooltipModifierPressed(modifier)
	local mod = tostring(modifier or "ALT"):upper()
	if mod == "SHIFT" then return IsShiftKeyDown and IsShiftKeyDown() end
	if mod == "CTRL" or mod == "CONTROL" then return IsControlKeyDown and IsControlKeyDown() end
	return IsAltKeyDown and IsAltKeyDown()
end

local function shouldShowTooltip(self)
	local st = self and self._eqolUFState or nil
	local mode = st and st._tooltipMode or nil
	local modifier = st and st._tooltipModifier or nil

	if not mode then
		local kind = (self and self._eqolGroupKind) or "party"
		local cfg = (self and (self._eqolCfg or getCfg(kind))) or getCfg(kind)
		local tc = cfg and cfg.tooltip or nil
		local def = (DEFAULTS[kind] and DEFAULTS[kind].tooltip) or (DEFAULTS.party and DEFAULTS.party.tooltip) or nil
		mode = tostring((tc and tc.mode) or (def and def.mode) or "OFF"):upper()
		modifier = tostring((tc and tc.modifier) or (def and def.modifier) or "ALT"):upper()
	else
		mode = tostring(mode):upper()
		modifier = tostring(modifier or "ALT"):upper()
	end

	if mode == "OFF" then return false end
	if mode == "ALWAYS" then return true end
	if mode == "OUT_OF_COMBAT" or mode == "OOC" then return not (InCombatLockdown and InCombatLockdown()) end
	if mode == "MODIFIER" then return isTooltipModifierPressed(modifier) end
	return false
end

function GF.EnsureHealPredictionCalculator(st)
	if not st or st._healPredictionCalcUnsupported then return nil end
	if st._healPredictionCalc then return st._healPredictionCalc end
	if not (CreateUnitHealPredictionCalculator and UnitGetDetailedHealPrediction) then
		st._healPredictionCalcUnsupported = true
		return nil
	end

	local calc = CreateUnitHealPredictionCalculator()
	if not calc then
		st._healPredictionCalcUnsupported = true
		return nil
	end
	if calc.SetIncomingHealOverflowPercent then calc:SetIncomingHealOverflowPercent(1) end
	st._healPredictionCalc = calc
	return calc
end

local function updateButtonConfig(self, cfg)
	if not self then return end
	cfg = cfg or self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	self._eqolCfg = cfg
	local st = getState(self)
	if not (st and cfg) then return end

	local tc = cfg.text or {}
	local hc = cfg.health or {}
	local pcfg = cfg.power or {}
	local ac = cfg.auras
	local scfg = cfg.status or {}

	st._wantsName = tc.showName ~= false
	st._wantsLevel = scfg.levelEnabled ~= false
	st._wantsIncomingHeal = hc.incomingHealEnabled == true
	st._wantsTempMaxHealthLoss = hc.tempMaxHealthLossEnabled ~= false
	st._wantsAbsorb = (hc.absorbEnabled ~= false) or (hc.healAbsorbEnabled ~= false)
	st._wantsStatusText = scfg and scfg.unitStatus and scfg.unitStatus.enabled ~= false
	st._wantsRangeFade = scfg and scfg.rangeFade and scfg.rangeFade.enabled ~= false
	st._wantsDispelTint = resolveDispelIndicatorEnabled(cfg, self._eqolGroupKind or "party")
	st._wantsPortrait = select(1, GF.ResolveGroupPortraitConfig(cfg, self._eqolGroupKind or "party"))

	local tooltipCfg = cfg.tooltip or {}
	local tooltipDef = (DEFAULTS[self._eqolGroupKind or "party"] and DEFAULTS[self._eqolGroupKind or "party"].tooltip) or (DEFAULTS.party and DEFAULTS.party.tooltip) or {}
	local tooltipMode = tostring(tooltipCfg.mode or tooltipDef.mode or "OFF"):upper()
	if tooltipMode == "OOC" then tooltipMode = "OUT_OF_COMBAT" end
	st._tooltipMode = tooltipMode
	st._tooltipModifier = tostring(tooltipCfg.modifier or tooltipDef.modifier or "ALT"):upper()
	st._tooltipUseEditMode = (tooltipCfg.useEditMode ~= nil and tooltipCfg.useEditMode == true) or ((tooltipCfg.useEditMode == nil) and tooltipDef.useEditMode == true)

	local wantsPower = true
	local powerHeight = cfg.powerHeight
	if powerHeight ~= nil and tonumber(powerHeight) <= 0 then wantsPower = false end
	if wantsPower and not canShowPowerBySelection(pcfg) then wantsPower = false end
	st._wantsPower = wantsPower

	local wantsAuras = false
	if ac then
		if ac.enabled == true then
			wantsAuras = true
		elseif ac.enabled == false then
			wantsAuras = false
		else
			wantsAuras = (ac.buff and ac.buff.enabled) or (ac.debuff and ac.debuff.enabled) or (ac.externals and ac.externals.enabled) or false
		end
	end
	st._wantsAuras = wantsAuras

	local wantsHealerBuffPlacement = false
	if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.IsEnabled then wantsHealerBuffPlacement = UF.GroupFramesHealerBuffs.IsEnabled(self._eqolGroupKind or "party", cfg) == true end
	if wantsHealerBuffPlacement and GF.IsHealerBuffPlacementSuppressedByBlizzard(self._eqolGroupKind or "party", cfg) then wantsHealerBuffPlacement = false end
	st._wantsHealerBuffPlacement = wantsHealerBuffPlacement
end

function GF:UnitButton_EvaluateUnit(self, forceUpdate)
	if not self then return false end
	local unit = getUnit(self)
	if not unitTokenExists(unit) then return false end
	if self.unit ~= unit then
		GF:UnitButton_SetUnit(self, unit)
		return true
	end
	if forceUpdate then
		GF:UpdateAll(self)
		return true
	end
	return false
end

function GF.UnitButton_OnShow(self)
	local st = self and self._eqolUFState
	if st then st._missedHiddenEvent = nil end

	GF:UnitButton_EvaluateUnit(self, true)
end

function GF:CacheUnitStatic(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st) then return end

	local guid = UnitGUID and UnitGUID(unit)
	if issecretvalue and issecretvalue(guid) then guid = nil end

	local cachedGuid = st._guid
	if issecretvalue and issecretvalue(cachedGuid) then
		cachedGuid = nil
		st._guid = nil
	end

	if cachedGuid == guid and st._unitToken == unit then
		if st._class and not (self._eqolPreview and isEditModeActive()) then return end
	end
	st._guid = guid
	st._unitToken = unit

	local class = getUnitClassToken(unit)
	if isEditModeActive() and self._eqolPreview and st._previewClass then class = st._previewClass end
	st._class = class
	if class then
		st._classR, st._classG, st._classB, st._classA = getClassColor(class)
	else
		st._classR, st._classG, st._classB, st._classA = nil, nil, nil, nil
	end
end

function GF:EnsureUnitClassColor(frame, st, unit)
	st = st or getState(frame)
	if not st then return false end
	if st._classR then return true end

	unit = unit or getUnit(frame)
	if not unit then return false end

	local class = st._class
	if isEditModeActive() and frame and frame._eqolPreview and st._previewClass then class = st._previewClass end
	if not class then class = getUnitClassToken(unit) end
	if not class then return false end

	st._class = class
	local r, g, b, a = getClassColor(class)
	if not r then return false end
	st._classR, st._classG, st._classB, st._classA = r, g, b, a or 1
	return true
end

function GF:NeedsClassColor(frame, st, cfg)
	if not frame then return false end
	cfg = cfg or frame._eqolCfg or getCfg(frame._eqolGroupKind or "party")
	local hc = cfg and cfg.health or {}
	local tc = cfg and cfg.text or {}
	local sc = cfg and cfg.status or {}

	if hc.useClassColor == true or hc.useTextClassColor == true then return true end
	if cfg.dataBar and cfg.dataBar.useTextClassColor == true then return true end

	local nameMode = sc.nameColorMode
	if nameMode == nil then nameMode = (tc.useClassColor ~= false) and "CLASS" or "CUSTOM" end
	if (st == nil or st._wantsName ~= false) and nameMode == "CLASS" then return true end

	if (st == nil or st._wantsLevel ~= false) and sc.levelColorMode == "CLASS" then return true end

	return false
end

function GF:BuildButton(self)
	if not self then return end
	local st = getState(self)

	local kind = self._eqolGroupKind or "party"
	local cfg = getCfg(kind)
	self._eqolCfg = cfg
	updateButtonConfig(self, cfg)
	local def = DEFAULTS[kind] or {}
	local hc = cfg.health or {}
	local defH = def.health or {}
	local pcfg = cfg.power or {}
	local tc = cfg.text or {}

	if not self._eqolUnitButtonOnShowHooked and self.HookScript then
		self._eqolUnitButtonOnShowHooked = true
		self:HookScript("OnShow", GF.UnitButton_OnShow)
	end

	_G.ClickCastFrames = _G.ClickCastFrames or {}
	_G.ClickCastFrames[self] = true

	if not st.barGroup then
		st.barGroup = CreateFrame("Frame", nil, self, "BackdropTemplate")
		st.barGroup:EnableMouse(false)
	end
	st.barGroup:SetAllPoints(self)

	if not st.layoutAnchor then
		st.layoutAnchor = CreateFrame("Frame", nil, self)
		st.layoutAnchor:EnableMouse(false)
	end
	if st.layoutAnchor.GetParent and st.layoutAnchor:GetParent() ~= self then st.layoutAnchor:SetParent(self) end
	st.layoutAnchor:SetAllPoints(self)

	setBackdrop(st.barGroup, cfg.border)
	if not st.portraitHolder then
		st.portraitHolder = CreateFrame("Frame", nil, st.barGroup, "BackdropTemplate")
		st.portraitHolder:EnableMouse(false)
		st.portraitHolder:Hide()
	end
	if not st.portrait then
		st.portrait = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(st.portraitHolder, nil, "ARTWORK")) or st.portraitHolder:CreateTexture(nil, "ARTWORK")
		st.portrait:SetTexCoord(0.08, 0.92, 0.08, 0.92)
		st.portrait:Hide()
	end
	if not st.portraitBg then
		st.portraitBg = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(st.portraitHolder, nil, "BACKGROUND")) or st.portraitHolder:CreateTexture(nil, "BACKGROUND")
		if Pixel and Pixel.SetColorTexture then
			Pixel.SetColorTexture(st.portraitBg, 0, 0, 0, 1)
		else
			st.portraitBg:SetColorTexture(0, 0, 0, 1)
		end
		st.portraitBg:Hide()
	end
	if not st.portraitSeparator then
		st.portraitSeparator = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(st.barGroup, nil, "ARTWORK")) or st.barGroup:CreateTexture(nil, "ARTWORK")
		if Pixel and Pixel.SetColorTexture then
			Pixel.SetColorTexture(st.portraitSeparator, 0, 0, 0, 1)
		else
			st.portraitSeparator:SetColorTexture(0, 0, 0, 1)
		end
		st.portraitSeparator:Hide()
	end
	if st.portrait and st.portrait:GetParent() ~= st.portraitHolder then st.portrait:SetParent(st.portraitHolder) end
	if st.portraitBg and st.portraitBg:GetParent() ~= st.portraitHolder then st.portraitBg:SetParent(st.portraitHolder) end
	if st.portraitHolder and st.portraitHolder:GetParent() ~= st.barGroup then st.portraitHolder:SetParent(st.barGroup) end
	if st.portraitSeparator and st.portraitSeparator:GetParent() ~= st.barGroup then st.portraitSeparator:SetParent(st.barGroup) end
	if not st.dispelTint then
		st.dispelTint = UFHelper.CreateDispelOverlay(st.barGroup)
		st.dispelTint:SetAllPoints(st.barGroup)
		st.dispelTint:Hide()
	end

	if not st.health then
		st.health = CreateFrame("StatusBar", nil, st.barGroup, "BackdropTemplate")
		st.health:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.health, 0, false, true)
		if st.health.SetStatusBarDesaturated then st.health:SetStatusBarDesaturated(true) end
	end
	if not st.tempMaxHealthLoss then
		st.tempMaxHealthLoss = CreateFrame("StatusBar", nil, st.health, "BackdropTemplate")
		st.tempMaxHealthLoss:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.tempMaxHealthLoss, 0, false, true)
		if st.tempMaxHealthLoss.SetStatusBarDesaturated then st.tempMaxHealthLoss:SetStatusBarDesaturated(false) end
		st.tempMaxHealthLoss:Hide()
	end
	if st.health.GetParent and st.health:GetParent() ~= st.barGroup then st.health:SetParent(st.barGroup) end
	if st.tempMaxHealthLoss.GetParent and st.tempMaxHealthLoss:GetParent() ~= st.health then st.tempMaxHealthLoss:SetParent(st.health) end
	local healthTexKey = getEffectiveBarTexture(cfg, hc)
	if st.health.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
		if Pixel and Pixel.SetStatusBarTexture then
			Pixel.SetStatusBarTexture(st.health, UFHelper.resolveTexture(healthTexKey))
		else
			st.health:SetStatusBarTexture(UFHelper.resolveTexture(healthTexKey))
		end
		if UFHelper.configureSpecialTexture then UFHelper.configureSpecialTexture(st.health, "HEALTH", healthTexKey, hc) end
		st._lastHealthTexture = healthTexKey
	end
	stabilizeStatusBarTexture(st.health)
	local healthBackdropClampToFill = (hc.backdrop and hc.backdrop.clampToFill)
	if healthBackdropClampToFill == nil then healthBackdropClampToFill = defH.backdrop and defH.backdrop.clampToFill end
	if healthBackdropClampToFill == nil then healthBackdropClampToFill = false end
	applyBarBackdrop(st.health, hc, { clampToFill = healthBackdropClampToFill == true, textureKey = healthTexKey })

	if not st.incomingHeal then
		st.incomingHeal = CreateFrame("StatusBar", nil, st.health, "BackdropTemplate")
		st.incomingHeal:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.incomingHeal, 0, false, true)
		if st.incomingHeal.SetStatusBarDesaturated then st.incomingHeal:SetStatusBarDesaturated(false) end
		st.incomingHeal:Hide()
	end
	if not st.absorb then
		st.absorb = CreateFrame("StatusBar", nil, st.health, "BackdropTemplate")
		st.absorb:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.absorb, 0, false, true)
		if st.absorb.SetStatusBarDesaturated then st.absorb:SetStatusBarDesaturated(false) end
		st.absorb:Hide()
	end
	if not st.overAbsorbGlow then
		st.overAbsorbGlow = st.health:CreateTexture(nil, "ARTWORK", "OverAbsorbGlowTemplate")
		if st.absorb then st.absorb.overAbsorbGlow = st.overAbsorbGlow end
		if not st.overAbsorbGlow then st.overAbsorbGlow = st.health:CreateTexture(nil, "ARTWORK") end
		if st.overAbsorbGlow then
			st.overAbsorbGlow:SetTexture(798066)
			st.overAbsorbGlow:SetBlendMode("ADD")
			if st.overAbsorbGlow.SetDrawLayer then st.overAbsorbGlow:SetDrawLayer("OVERLAY", 7) end
			st.overAbsorbGlow:SetAlpha(0.8)
			st.overAbsorbGlow:Hide()
		end
	end
	if not st.healAbsorb then
		st.healAbsorb = CreateFrame("StatusBar", nil, st.health, "BackdropTemplate")
		st.healAbsorb:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.healAbsorb, 0, false, true)
		if st.healAbsorb.SetStatusBarDesaturated then st.healAbsorb:SetStatusBarDesaturated(false) end
		st.healAbsorb:Hide()
	end

	if not st.power then
		st.power = CreateFrame("StatusBar", nil, st.barGroup, "BackdropTemplate")
		st.power:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.power, 0, false, true)
	end
	local powerTexKey = getEffectiveBarTexture(cfg, pcfg)
	if st.power.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
		if Pixel and Pixel.SetStatusBarTexture then
			Pixel.SetStatusBarTexture(st.power, UFHelper.resolveTexture(powerTexKey))
		else
			st.power:SetStatusBarTexture(UFHelper.resolveTexture(powerTexKey))
		end
		st._lastPowerTexture = powerTexKey
	end
	applyBarBackdrop(st.power, pcfg, { textureKey = powerTexKey })
	if st.power.SetStatusBarDesaturated then st.power:SetStatusBarDesaturated(true) end

	if not st.dataBar then
		st.dataBar = CreateFrame("StatusBar", nil, st.barGroup, "BackdropTemplate")
		st.dataBar:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.dataBar, 1, false, true)
		if st.dataBar.SetStatusBarDesaturated then st.dataBar:SetStatusBarDesaturated(false) end
		st.dataBar:Hide()
	end
	if st.dataBar.GetParent and st.dataBar:GetParent() ~= st.barGroup then st.dataBar:SetParent(st.barGroup) end

	if not st.healthTextLayer then
		st.healthTextLayer = CreateFrame("Frame", nil, st.health)
		st.healthTextLayer:SetAllPoints(st.health)
	end
	if not st.powerTextLayer then
		st.powerTextLayer = CreateFrame("Frame", nil, st.power)
		st.powerTextLayer:SetAllPoints(st.power)
	end
	if not st.dataBarTextLayer then
		st.dataBarTextLayer = CreateFrame("Frame", nil, st.dataBar)
		st.dataBarTextLayer:SetAllPoints(st.dataBar)
	end
	if st.dataBarTextLayer.GetParent and st.dataBarTextLayer:GetParent() ~= st.dataBar then st.dataBarTextLayer:SetParent(st.dataBar) end
	st.dataBarTextLayer:ClearAllPoints()
	st.dataBarTextLayer:SetAllPoints(st.dataBar)
	local textOverlayParent = st.layoutAnchor or st.barGroup or st.health or self
	local nameTextLayer = GF.EnsureTextOverlayLayer(st, "nameTextLayer", textOverlayParent)
	local levelTextLayer = GF.EnsureTextOverlayLayer(st, "levelTextLayer", textOverlayParent)
	if not st.statusIconLayer then
		st.statusIconLayer = CreateFrame("Frame", nil, st.layoutAnchor or st.barGroup)
		st.statusIconLayer:SetAllPoints(st.layoutAnchor or st.barGroup)
		st.statusIconLayer:EnableMouse(false)
	end
	if st.statusIconLayer.GetParent and st.statusIconLayer:GetParent() ~= (st.layoutAnchor or st.barGroup) then st.statusIconLayer:SetParent(st.layoutAnchor or st.barGroup) end
	if st.dispelTint then GF.SyncDispelTintLayer(st) end

	if not st.healthTextLeft then st.healthTextLeft = st.healthTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.healthTextCenter then st.healthTextCenter = st.healthTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.healthTextRight then st.healthTextRight = st.healthTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.powerTextLeft then st.powerTextLeft = st.powerTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.powerTextCenter then st.powerTextCenter = st.powerTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.powerTextRight then st.powerTextRight = st.powerTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.dataBarTextLeft then st.dataBarTextLeft = st.dataBarTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.dataBarTextCenter then st.dataBarTextCenter = st.dataBarTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.dataBarTextRight then st.dataBarTextRight = st.dataBarTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end

	if not st.nameText then st.nameText = nameTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if st.nameText.GetParent and st.nameText:GetParent() ~= nameTextLayer then st.nameText:SetParent(nameTextLayer) end
	st.name = st.nameText
	if not st.levelText then st.levelText = levelTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if st.levelText.GetParent and st.levelText:GetParent() ~= levelTextLayer then st.levelText:SetParent(levelTextLayer) end
	if not st.statusText then st.statusText = st.healthTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.groupNumberText then st.groupNumberText = st.healthTextLayer:CreateFontString(nil, "OVERLAY", "GameFontHighlight") end
	if not st.privateAuras then
		st.privateAuras = CreateFrame("Frame", nil, st.layoutAnchor or st.barGroup or st.health or self)
		st.privateAuras:EnableMouse(false)
	end
	local privateAuraParent = st.layoutAnchor or st.barGroup or st.health or self
	if st.privateAuras.GetParent and privateAuraParent and st.privateAuras:GetParent() ~= privateAuraParent then st.privateAuras:SetParent(privateAuraParent) end

	local indicatorLayer = st.statusIconLayer or st.healthTextLayer
	if not st.leaderIcon then
		st.leaderIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
	end
	if not st.assistIcon then
		st.assistIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
	end
	if not st.raidAssignmentIcon then
		st.raidAssignmentIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
	end
	if not st.raidIcon then
		st.raidIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.raidIcon, "Interface\\TargetingFrame\\UI-RaidTargetingIcons")
		else
			st.raidIcon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons")
		end
		if Pixel and Pixel.SetSize then
			Pixel.SetSize(st.raidIcon, 18, 18, 1, 1)
		else
			st.raidIcon:SetSize(18, 18)
		end
		st.raidIcon:Hide()
	end
	if not st.readyCheckIcon then
		st.readyCheckIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.readyCheckIcon, GFH.STATUS_ICON_CONST.waiting)
		else
			st.readyCheckIcon:SetTexture(GFH.STATUS_ICON_CONST.waiting)
		end
		if Pixel and Pixel.SetSize then
			Pixel.SetSize(st.readyCheckIcon, 16, 16, 1, 1)
		else
			st.readyCheckIcon:SetSize(16, 16)
		end
		st.readyCheckIcon:Hide()
	end
	if not st.summonIcon then
		st.summonIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
		if Pixel and Pixel.SetSize then
			Pixel.SetSize(st.summonIcon, 16, 16, 1, 1)
		else
			st.summonIcon:SetSize(16, 16)
		end
		st.summonIcon:Hide()
	end
	if not st.resurrectIcon then
		st.resurrectIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.resurrectIcon, GFH.STATUS_ICON_CONST.resurrect)
		else
			st.resurrectIcon:SetTexture(GFH.STATUS_ICON_CONST.resurrect)
		end
		if Pixel and Pixel.SetSize then
			Pixel.SetSize(st.resurrectIcon, 16, 16, 1, 1)
		else
			st.resurrectIcon:SetSize(16, 16)
		end
		st.resurrectIcon:Hide()
	end
	if not st.phaseIcon then
		st.phaseIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.phaseIcon, GFH.STATUS_ICON_CONST.phase)
		else
			st.phaseIcon:SetTexture(GFH.STATUS_ICON_CONST.phase)
		end
		if Pixel and Pixel.SetSize then
			Pixel.SetSize(st.phaseIcon, 14, 14, 1, 1)
		else
			st.phaseIcon:SetSize(14, 14)
		end
		st.phaseIcon:Hide()
	end
	if st.leaderIcon.GetParent and st.leaderIcon:GetParent() ~= indicatorLayer then st.leaderIcon:SetParent(indicatorLayer) end
	if st.assistIcon.GetParent and st.assistIcon:GetParent() ~= indicatorLayer then st.assistIcon:SetParent(indicatorLayer) end
	if st.raidAssignmentIcon.GetParent and st.raidAssignmentIcon:GetParent() ~= indicatorLayer then st.raidAssignmentIcon:SetParent(indicatorLayer) end
	if st.raidIcon.GetParent and st.raidIcon:GetParent() ~= indicatorLayer then st.raidIcon:SetParent(indicatorLayer) end
	if st.readyCheckIcon.GetParent and st.readyCheckIcon:GetParent() ~= indicatorLayer then st.readyCheckIcon:SetParent(indicatorLayer) end
	if st.summonIcon.GetParent and st.summonIcon:GetParent() ~= indicatorLayer then st.summonIcon:SetParent(indicatorLayer) end
	if st.resurrectIcon.GetParent and st.resurrectIcon:GetParent() ~= indicatorLayer then st.resurrectIcon:SetParent(indicatorLayer) end
	if st.phaseIcon.GetParent and st.phaseIcon:GetParent() ~= indicatorLayer then st.phaseIcon:SetParent(indicatorLayer) end
	if st.leaderIcon.SetDrawLayer then st.leaderIcon:SetDrawLayer("OVERLAY", 7) end
	if st.assistIcon.SetDrawLayer then st.assistIcon:SetDrawLayer("OVERLAY", 7) end
	if st.raidAssignmentIcon.SetDrawLayer then st.raidAssignmentIcon:SetDrawLayer("OVERLAY", 7) end
	if st.raidIcon.SetDrawLayer then st.raidIcon:SetDrawLayer("OVERLAY", 7) end
	if st.readyCheckIcon.SetDrawLayer then st.readyCheckIcon:SetDrawLayer("OVERLAY", 7) end
	if st.summonIcon.SetDrawLayer then st.summonIcon:SetDrawLayer("OVERLAY", 7) end
	if st.resurrectIcon.SetDrawLayer then st.resurrectIcon:SetDrawLayer("OVERLAY", 7) end
	if st.phaseIcon.SetDrawLayer then st.phaseIcon:SetDrawLayer("OVERLAY", 7) end

	if UFHelper and UFHelper.applyFont then
		UFHelper.applyFont(st.healthTextLeft, hc.font, hc.fontSize or 12, hc.fontOutline)
		UFHelper.applyFont(st.healthTextCenter, hc.font, hc.fontSize or 12, hc.fontOutline)
		UFHelper.applyFont(st.healthTextRight, hc.font, hc.fontSize or 12, hc.fontOutline)
		UFHelper.applyFont(st.powerTextLeft, pcfg.font, pcfg.fontSize or 10, pcfg.fontOutline)
		UFHelper.applyFont(st.powerTextCenter, pcfg.font, pcfg.fontSize or 10, pcfg.fontOutline)
		UFHelper.applyFont(st.powerTextRight, pcfg.font, pcfg.fontSize or 10, pcfg.fontOutline)
		local dbc = cfg.dataBar or {}
		UFHelper.applyFont(st.dataBarTextLeft, dbc.font, dbc.fontSize or 12, dbc.fontOutline)
		UFHelper.applyFont(st.dataBarTextCenter, dbc.font, dbc.fontSize or 12, dbc.fontOutline)
		UFHelper.applyFont(st.dataBarTextRight, dbc.font, dbc.fontSize or 12, dbc.fontOutline)
		UFHelper.applyFont(st.nameText, tc.font or hc.font, tc.fontSize or hc.fontSize or 12, tc.fontOutline or hc.fontOutline)
	end

	if not st._dispelOnHideHooked then
		st._dispelOnHideHooked = true
		self:HookScript("OnHide", function(btn)
			local s = getState(btn)
			hideDispelTint(s)
			GF.StopDispelGlow((s and s.barGroup) or btn, nil, s)
		end)
	end

	if not st._menuHooked then
		st._menuHooked = true
		self.menu = function(btn) GF:OpenUnitMenu(btn) end
	end

	if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.BuildButton then UF.GroupFramesHealerBuffs.BuildButton(self) end
	GF:LayoutAuras(self)
	hookTextFrameLevels(st)
	GF:LayoutButton(self)
end

function GF:LayoutButton(self)
	if not self then return end
	local st = getState(self)
	if not (st and st.barGroup and st.health and st.power) then return end

	local kind = self._eqolGroupKind
	local cfg = self._eqolCfg or getCfg(kind or "party")
	local def = DEFAULTS[kind] or {}
	local hc = cfg.health or {}
	local pcfg = cfg.power or {}
	local dbc = cfg.dataBar or {}
	local defH = def.health or {}
	local defDB = def.dataBar or {}
	local healthBackdropClampToFill = (hc.backdrop and hc.backdrop.clampToFill)
	if healthBackdropClampToFill == nil then healthBackdropClampToFill = defH.backdrop and defH.backdrop.clampToFill end
	if healthBackdropClampToFill == nil then healthBackdropClampToFill = false end
	local healthTexKey = getEffectiveBarTexture(cfg, hc)
	local powerTexKey = getEffectiveBarTexture(cfg, pcfg)
	local dataBarTexKey = dbc.texture or defDB.texture or "SOLID"
	local contentScale = GF.GetDynamicContentScale(self, cfg)

	local scale = GFH.GetEffectiveScale(self)
	if not scale or scale <= 0 then scale = (UIParent and UIParent.GetEffectiveScale and UIParent:GetEffectiveScale()) or 1 end

	local powerDetachedRequested = GF.IsGroupPowerDetached(cfg, kind)
	local powerH = tonumber(powerDetachedRequested and pcfg.detachedHeight)
	if powerH == nil then powerH = tonumber(cfg.powerHeight) end
	if powerH == nil then powerH = tonumber(def.powerHeight) or 6 end
	if st._powerHidden then powerH = 0 end
	local w, h = self:GetSize()
	if not w or not h then return end
	local availH = h
	if availH < 1 then availH = 1 end
	if not powerDetachedRequested and powerH > availH - 4 then powerH = math.max(0, availH * 0.25) end
	powerH = roundToEvenPixel(max(0, powerH), scale)
	if not powerDetachedRequested and powerH > availH then powerH = availH end
	local powerDetached = powerDetachedRequested and powerH > 0
	local dataBarEnabled = dbc.enabled == true
	local dataBarDetached = dataBarEnabled and dbc.detached == true
	local dataBarH = dataBarEnabled and clampNumber(dbc.height, 1, dataBarDetached and 1000 or availH, defDB.height or 16) or 0
	local dataBarGap = dataBarEnabled and clampNumber(dbc.gap, -20, 40, defDB.gap or 0) or 0
	dataBarH = roundToEvenPixel(dataBarH, scale)
	dataBarGap = roundToPixel(dataBarGap, scale)
	local dataBarSpace = (dataBarEnabled and not dataBarDetached) and max(0, dataBarH + dataBarGap) or 0
	if dataBarSpace > availH - 1 then dataBarSpace = max(0, availH - 1) end
	local dataBarPosition = tostring(dbc.position or defDB.position or "BELOW"):upper()
	if dataBarPosition ~= "ABOVE" then dataBarPosition = "BELOW" end

	local portraitEnabled, portraitSide, portraitSquareBackground, portraitBorderWithFrame = GF.ResolveGroupPortraitConfig(cfg, kind)
	local portraitDetached, portraitDetachedX, portraitDetachedY, portraitDetachedSize = GF.ResolveGroupPortraitDetachedConfig(cfg, kind)
	portraitDetached = portraitEnabled and portraitDetached == true
	local portraitBaseSize = max(1, portraitDetached and portraitDetachedSize or availH)
	local portraitSize = portraitEnabled and roundToEvenPixel(portraitBaseSize, scale) or 0
	if portraitDetached and (portraitDetachedX == nil or portraitDetachedY == nil) then
		local defaultPortraitX, defaultPortraitY = GF.GetDefaultDetachedPortraitOffset(cfg, kind, w, h, portraitSize)
		if portraitDetachedX == nil then portraitDetachedX = defaultPortraitX end
		if portraitDetachedY == nil then portraitDetachedY = defaultPortraitY end
	end
	portraitDetachedX = portraitDetachedX or 0
	portraitDetachedY = portraitDetachedY or 0
	local borderCfg = cfg.border or {}
	local borderDef = def.border or {}
	local frameBorderEnabled = borderCfg.enabled
	if frameBorderEnabled == nil then frameBorderEnabled = borderDef.enabled end
	frameBorderEnabled = frameBorderEnabled == true
	-- Match the regular unit-frame behavior: when the frame border should include the portrait,
	-- expand the bordered frame towards the portrait side so the bars keep their width.
	local portraitInsideFrame = portraitEnabled and not portraitDetached and portraitBorderWithFrame and frameBorderEnabled
	local portraitOutside = not portraitInsideFrame
	local separatorEnabled, separatorSize = GF.ResolveGroupPortraitSeparatorConfig(cfg, kind, portraitEnabled)
	local separatorSpace = (separatorEnabled and separatorSize > 0) and separatorSize or 0
	if portraitEnabled then
		if portraitSize < 1 then
			portraitEnabled = false
			portraitSize = 0
			separatorSpace = 0
		end
	end
	local portraitSpace = (portraitEnabled and not portraitDetached) and (portraitSize + separatorSpace) or 0
	local contentOffsetLeft = 0
	local contentOffsetRight = 0
	local layoutAnchor = GF.GetLayoutAnchorFrame(st, self) or self
	if not portraitOutside then
		contentOffsetLeft = (portraitEnabled and portraitSide == "LEFT") and portraitSpace or 0
		contentOffsetRight = (portraitEnabled and portraitSide == "RIGHT") and portraitSpace or 0
	end

	local bottomDataBarSpace = dataBarEnabled and dataBarPosition == "BELOW" and dataBarSpace or 0
	local topDataBarSpace = dataBarEnabled and dataBarPosition == "ABOVE" and dataBarSpace or 0
	local healthBottomOffset = (powerDetached and 0 or roundToPixel(powerH, scale)) + bottomDataBarSpace

	st.barGroup:ClearAllPoints()
	if portraitInsideFrame and portraitSpace > 0 then
		if portraitSide == "RIGHT" then
			st.barGroup:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
			st.barGroup:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", portraitSpace, 0)
		else
			st.barGroup:SetPoint("TOPLEFT", self, "TOPLEFT", -portraitSpace, 0)
			st.barGroup:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
		end
	else
		st.barGroup:SetAllPoints(self)
	end
	if st.layoutAnchor then
		st.layoutAnchor:ClearAllPoints()
		st.layoutAnchor:SetAllPoints(self)
		if st.layoutAnchor.SetFrameStrata and st.barGroup.GetFrameStrata then st.layoutAnchor:SetFrameStrata(st.barGroup:GetFrameStrata()) end
		if st.layoutAnchor.SetFrameLevel and st.barGroup.GetFrameLevel then st.layoutAnchor:SetFrameLevel(GF.ClampFrameLevel(st.barGroup:GetFrameLevel() or 0)) end
	end
	setBackdrop(st.barGroup, cfg.border)

	st._highlightHoverCfg = buildHighlightConfig(cfg, def, "highlightHover")
	st._highlightAggroCfg = buildHighlightConfig(cfg, def, "highlightAggro")
	st._highlightTargetCfg = buildHighlightConfig(cfg, def, "highlightTarget")
	applyHighlightStyle(st, st._highlightHoverCfg, "hover")
	applyHighlightStyle(st, st._highlightAggroCfg, "aggro")
	applyHighlightStyle(st, st._highlightTargetCfg, "target")
	st._wantsAggroHighlight = st._highlightAggroCfg and st._highlightAggroCfg.enabled == true or false

	st.power:ClearAllPoints()
	if Pixel and Pixel.SetHeight then
		Pixel.SetHeight(st.power, powerH)
	else
		st.power:SetHeight(powerH)
	end

	if st.dataBar then
		st.dataBar:ClearAllPoints()
		if dataBarEnabled and dataBarH > 0 then
			if Pixel and Pixel.SetHeight then
				Pixel.SetHeight(st.dataBar, dataBarH)
			else
				st.dataBar:SetHeight(dataBarH)
			end
			if dataBarDetached then
				local dataBarOffset = dbc.detachedOffset or EMPTY
				local dataBarX = tonumber(dataBarOffset.x) or 0
				local dataBarY = tonumber(dataBarOffset.y) or 0
				local dataBarGrowFromCenter = dbc.detachedGrowFromCenter == true
				local dataBarMatchHealthWidth = dbc.detachedMatchHealthWidth ~= false
				local dataBarWidth = max(1, w - contentOffsetLeft - contentOffsetRight)
				if not dataBarMatchHealthWidth then dataBarWidth = max(1, tonumber(dbc.detachedWidth) or dataBarWidth) end
				if Pixel and Pixel.SetWidth then
					Pixel.SetWidth(st.dataBar, dataBarWidth, 1)
				else
					st.dataBar:SetWidth(dataBarWidth)
				end
				if dataBarPosition == "ABOVE" then
					if dataBarGrowFromCenter then
						st.dataBar:SetPoint("BOTTOM", st.health, "TOP", dataBarX, dataBarGap + dataBarY)
					else
						st.dataBar:SetPoint("BOTTOMLEFT", st.health, "TOPLEFT", dataBarX, dataBarGap + dataBarY)
					end
				else
					local dataBarAnchor = (powerDetached and not st._powerHidden) and st.power or st.health
					if dataBarGrowFromCenter then
						st.dataBar:SetPoint("TOP", dataBarAnchor, "BOTTOM", dataBarX, -dataBarGap + dataBarY)
					else
						st.dataBar:SetPoint("TOPLEFT", dataBarAnchor, "BOTTOMLEFT", dataBarX, -dataBarGap + dataBarY)
					end
				end
				local detachedDataBarStrata = GF.NormalizeFrameStrataToken(dbc.detachedStrata)
				if st.dataBar.SetFrameStrata then
					if detachedDataBarStrata then
						st.dataBar:SetFrameStrata(detachedDataBarStrata)
					elseif st.health.GetFrameStrata then
						st.dataBar:SetFrameStrata(st.health:GetFrameStrata())
					end
				end
				if st.dataBar.SetFrameLevel and st.health.GetFrameLevel then
					local levelOffset = clampNumber(dbc.detachedFrameLevelOffset, -20, 1000, 2)
					levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
					st.dataBar:SetFrameLevel(GF.ClampFrameLevel((st.health:GetFrameLevel() or 0) + levelOffset))
				end
			elseif dataBarPosition == "ABOVE" then
				st.dataBar:SetPoint("TOPLEFT", st.barGroup, "TOPLEFT", contentOffsetLeft, 0)
				st.dataBar:SetPoint("TOPRIGHT", st.barGroup, "TOPRIGHT", -contentOffsetRight, 0)
			else
				st.dataBar:SetPoint("BOTTOMLEFT", st.barGroup, "BOTTOMLEFT", contentOffsetLeft, 0)
				st.dataBar:SetPoint("BOTTOMRIGHT", st.barGroup, "BOTTOMRIGHT", -contentOffsetRight, 0)
			end
			if not dataBarDetached then
				if st.dataBar.SetFrameStrata and st.barGroup.GetFrameStrata then st.dataBar:SetFrameStrata(st.barGroup:GetFrameStrata()) end
				if st.dataBar.SetFrameLevel and st.health.GetFrameLevel then st.dataBar:SetFrameLevel(GF.ClampFrameLevel((st.health:GetFrameLevel() or 0) + 1)) end
			end
			st.dataBar:Show()
		else
			st.dataBar:Hide()
			if st.dataBarTextLeft then st.dataBarTextLeft:SetText("") end
			if st.dataBarTextCenter then st.dataBarTextCenter:SetText("") end
			if st.dataBarTextRight then st.dataBarTextRight:SetText("") end
			st._lastDataBarTextLeft, st._lastDataBarTextCenter, st._lastDataBarTextRight = nil, nil, nil
		end
	end

	st.health:ClearAllPoints()
	st.health:SetPoint("TOPLEFT", st.barGroup, "TOPLEFT", contentOffsetLeft, -topDataBarSpace)
	st.health:SetPoint("BOTTOMRIGHT", st.barGroup, "BOTTOMRIGHT", -contentOffsetRight, healthBottomOffset)
	if st.tempMaxHealthLoss then
		if st.tempMaxHealthLoss.GetParent and st.tempMaxHealthLoss:GetParent() ~= st.health then st.tempMaxHealthLoss:SetParent(st.health) end
		st.tempMaxHealthLoss:ClearAllPoints()
		st.tempMaxHealthLoss:SetAllPoints(st.health)
	end
	local tempMaxHealthLossEnabled = hc.tempMaxHealthLossEnabled
	if tempMaxHealthLossEnabled == nil then tempMaxHealthLossEnabled = defH.tempMaxHealthLossEnabled ~= false end
	applyBarBackdrop(st.health, hc, { clampToFill = healthBackdropClampToFill == true, textureKey = healthTexKey })
	applyBarBackdrop(st.power, pcfg, { textureKey = powerTexKey })
	if st.dataBar and st.dataBar.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
		if st._lastDataBarTexture ~= dataBarTexKey then
			if Pixel and Pixel.SetStatusBarTexture then
				Pixel.SetStatusBarTexture(st.dataBar, UFHelper.resolveTexture(dataBarTexKey))
			else
				st.dataBar:SetStatusBarTexture(UFHelper.resolveTexture(dataBarTexKey))
			end
			st._lastDataBarTexture = dataBarTexKey
			stabilizeStatusBarTexture(st.dataBar)
		end
	end
	if st.dataBar then applyBarBackdrop(st.dataBar, { backdrop = { enabled = false }, texture = dataBarTexKey }, { textureKey = dataBarTexKey }) end

	if powerDetached then
		local powerOffset = pcfg.offset or EMPTY
		local powerX = tonumber(powerOffset.x) or 0
		local powerY = tonumber(powerOffset.y) or 0
		local powerGrowFromCenter = pcfg.detachedGrowFromCenter == true
		local powerMatchHealthWidth = pcfg.detachedMatchHealthWidth ~= false
		local powerWidth = max(1, w - contentOffsetLeft - contentOffsetRight)
		if not powerMatchHealthWidth then powerWidth = max(1, tonumber(pcfg.width) or powerWidth) end
		if Pixel and Pixel.SetWidth then
			Pixel.SetWidth(st.power, powerWidth, 1)
		else
			st.power:SetWidth(powerWidth)
		end
		if powerGrowFromCenter then
			st.power:SetPoint("TOP", st.health, "BOTTOM", powerX, powerY)
		else
			st.power:SetPoint("TOPLEFT", st.health, "BOTTOMLEFT", powerX, powerY)
		end
		local detachedPowerStrata = GF.NormalizeFrameStrataToken(pcfg.detachedStrata)
		if st.power.SetFrameStrata then
			if detachedPowerStrata then
				st.power:SetFrameStrata(detachedPowerStrata)
			elseif st.health.GetFrameStrata then
				st.power:SetFrameStrata(st.health:GetFrameStrata())
			end
		end
		if st.power.SetFrameLevel and st.health.GetFrameLevel then
			local levelOffset = clampNumber(pcfg.detachedFrameLevelOffset, -20, 1000, 2)
			levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
			local powerLevel = GF.ClampFrameLevel((st.health:GetFrameLevel() or 0) + levelOffset)
			st.power:SetFrameLevel(powerLevel)
		end
	else
		st.power:SetPoint("BOTTOMLEFT", st.barGroup, "BOTTOMLEFT", contentOffsetLeft, bottomDataBarSpace)
		st.power:SetPoint("BOTTOMRIGHT", st.barGroup, "BOTTOMRIGHT", -contentOffsetRight, bottomDataBarSpace)
		if st.power.SetFrameStrata and st.barGroup.GetFrameStrata then st.power:SetFrameStrata(st.barGroup:GetFrameStrata()) end
		if st.power.SetFrameLevel and st.health.GetFrameLevel then st.power:SetFrameLevel(GF.ClampFrameLevel((st.health:GetFrameLevel() or 0) + 1)) end
	end
	local detachedPowerBorderCfg
	if powerDetached and pcfg.detachedBorder == true then
		local detachedPowerBorderSize = pcfg.detachedBorderSize
		if detachedPowerBorderSize == nil then detachedPowerBorderSize = borderCfg.edgeSize or borderDef.edgeSize end
		local detachedPowerBorderOffset = pcfg.detachedBorderOffset
		if detachedPowerBorderOffset == nil then detachedPowerBorderOffset = borderCfg.offset end
		if detachedPowerBorderOffset == nil then detachedPowerBorderOffset = borderDef.offset end
		if detachedPowerBorderOffset == nil then detachedPowerBorderOffset = detachedPowerBorderSize end
		detachedPowerBorderCfg = {
			enabled = true,
			texture = pcfg.detachedBorderTexture or borderCfg.texture or borderDef.texture,
			edgeSize = detachedPowerBorderSize,
			color = borderCfg.color or borderDef.color,
			offset = detachedPowerBorderOffset,
			inset = borderCfg.inset ~= nil and borderCfg.inset or borderDef.inset,
			strata = GF.NormalizeFrameStrataToken(pcfg.detachedStrata) or borderCfg.strata or borderDef.strata,
			frameLevelOffset = borderCfg.frameLevelOffset ~= nil and borderCfg.frameLevelOffset or borderDef.frameLevelOffset,
		}
	end
	setBackdrop(st.power, detachedPowerBorderCfg, true)

	st._portraitEnabled = portraitEnabled
	st._portraitSide = portraitSide
	st._portraitSize = portraitSize
	st._portraitSquareBackground = portraitSquareBackground
	st._portraitSpace = portraitSpace
	st._portraitCenterOffset = 0
	if st.portraitHolder then
		if portraitEnabled then
			local holderParent = st.barGroup or self
			local holderOffset = portraitSize / 2
			if portraitOutside then holderOffset = holderOffset + separatorSpace end
			local holderX = holderOffset
			if portraitSide == "RIGHT" then
				holderX = portraitOutside and holderOffset or -holderOffset
			else
				holderX = portraitOutside and -holderOffset or holderOffset
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.portraitHolder, portraitSize, portraitSize, 1, 1)
			else
				st.portraitHolder:SetSize(portraitSize, portraitSize)
			end
			st.portraitHolder:ClearAllPoints()
			if portraitDetached then
				st.portraitHolder:SetPoint("CENTER", self, "CENTER", portraitDetachedX, portraitDetachedY)
			elseif portraitSide == "RIGHT" then
				st.portraitHolder:SetPoint("CENTER", holderParent, "RIGHT", holderX, 0)
			else
				st.portraitHolder:SetPoint("CENTER", holderParent, "LEFT", holderX, 0)
			end
			if holderParent and st.portraitHolder.SetFrameStrata and holderParent.GetFrameStrata then
				local portraitCfg = cfg.portrait or EMPTY
				local portraitStrata = portraitDetached and GF.NormalizeFrameStrataToken(portraitCfg.detachedStrata) or nil
				if portraitStrata then
					st.portraitHolder:SetFrameStrata(portraitStrata)
				else
					st.portraitHolder:SetFrameStrata(holderParent:GetFrameStrata())
				end
				local levelOffset = 1
				if portraitDetached and portraitCfg.detachedFrameLevelOffset ~= nil then
					levelOffset = clampNumber(portraitCfg.detachedFrameLevelOffset, -20, 1000, 1)
					levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
				end
				local portraitLevel = GF.ClampFrameLevel((holderParent:GetFrameLevel() or 0) + levelOffset)
				st.portraitHolder:SetFrameLevel(portraitLevel)
			end
			if st.portrait then
				if Pixel and Pixel.SetSize then
					Pixel.SetSize(st.portrait, portraitSize, portraitSize, 1, 1)
				else
					st.portrait:SetSize(portraitSize, portraitSize)
				end
				st.portrait:ClearAllPoints()
				st.portrait:SetPoint("CENTER", st.portraitHolder, "CENTER", 0, 0)
			end
			if st.portraitBg then
				if portraitSquareBackground == true then
					st.portraitBg:ClearAllPoints()
					st.portraitBg:SetAllPoints(st.portrait)
					st.portraitBg:Show()
				else
					st.portraitBg:Hide()
				end
			end
			st.portraitHolder:Show()
		else
			if st.portrait then st.portrait:Hide() end
			if st.portraitBg then st.portraitBg:Hide() end
			st.portraitHolder:Hide()
		end
		local portraitBorderCfg
		if portraitDetached and portraitBorderWithFrame and frameBorderEnabled then
			portraitBorderCfg = {
				enabled = true,
				texture = borderCfg.texture or borderDef.texture,
				edgeSize = borderCfg.edgeSize or borderDef.edgeSize,
				color = borderCfg.color or borderDef.color,
				offset = borderCfg.offset ~= nil and borderCfg.offset or borderDef.offset,
				inset = borderCfg.inset ~= nil and borderCfg.inset or borderDef.inset,
				strata = borderCfg.strata or borderDef.strata,
				frameLevelOffset = borderCfg.frameLevelOffset ~= nil and borderCfg.frameLevelOffset or borderDef.frameLevelOffset,
			}
		end
		setBackdrop(st.portraitHolder, portraitBorderCfg)
	end
	if st.portraitSeparator then
		local separatorParent = portraitDetached and st.portraitHolder or st.barGroup
		if separatorParent and st.portraitSeparator.GetParent and st.portraitSeparator:GetParent() ~= separatorParent then st.portraitSeparator:SetParent(separatorParent) end
	end
	GF.ApplyGroupPortraitSeparator(cfg, kind, st, portraitEnabled and not portraitDetached)

	self.powerBarUsedHeight = (not powerDetached and powerH > 0 and not st._powerHidden) and healthBottomOffset or 0
	if st.dispelTint then
		GF.SyncDispelTintLayer(st)
		if st.dispelTint.SetOrientation and DispelOverlayOrientation then st.dispelTint:SetOrientation(DispelOverlayOrientation.VerticalTopToBottom, 0, 0) end
	end

	if UFHelper and UFHelper.applyFont then
		GF.ApplyScaledFont(self, st.healthTextLeft, hc.font, hc.fontSize or 12, hc.fontOutline, cfg)
		GF.ApplyScaledFont(self, st.healthTextCenter, hc.font, hc.fontSize or 12, hc.fontOutline, cfg)
		GF.ApplyScaledFont(self, st.healthTextRight, hc.font, hc.fontSize or 12, hc.fontOutline, cfg)
		local pcfgLocal = cfg.power or {}
		GF.ApplyScaledFont(self, st.powerTextLeft, pcfgLocal.font, pcfgLocal.fontSize or 10, pcfgLocal.fontOutline, cfg)
		GF.ApplyScaledFont(self, st.powerTextCenter, pcfgLocal.font, pcfgLocal.fontSize or 10, pcfgLocal.fontOutline, cfg)
		GF.ApplyScaledFont(self, st.powerTextRight, pcfgLocal.font, pcfgLocal.fontSize or 10, pcfgLocal.fontOutline, cfg)
		local dbcLocal = cfg.dataBar or {}
		GF.ApplyScaledFont(self, st.dataBarTextLeft, dbcLocal.font, dbcLocal.fontSize or 12, dbcLocal.fontOutline, cfg)
		GF.ApplyScaledFont(self, st.dataBarTextCenter, dbcLocal.font, dbcLocal.fontSize or 12, dbcLocal.fontOutline, cfg)
		GF.ApplyScaledFont(self, st.dataBarTextRight, dbcLocal.font, dbcLocal.fontSize or 12, dbcLocal.fontOutline, cfg)
		if st.statusText then
			local scfg = cfg.status or {}
			local us = scfg.unitStatus or {}
			GF.ApplyScaledFont(self, st.statusText, us.font or hc.font, us.fontSize or hc.fontSize or 12, us.fontOutline or hc.fontOutline, cfg)
		end
		if st.groupNumberText then
			local style = resolveGroupNumberStyle(cfg, def, hc)
			UFHelper.applyFont(st.groupNumberText, style.font, style.fontSize or 12, style.fontOutline)
		end
	end
	layoutTexts(st.health, st.healthTextLeft, st.healthTextCenter, st.healthTextRight, GF.GetScaledBarTextConfig(cfg.health, contentScale), scale, layoutAnchor or st.health)
	layoutTexts(st.power, st.powerTextLeft, st.powerTextCenter, st.powerTextRight, GF.GetScaledBarTextConfig(cfg.power, contentScale), scale)
	layoutTexts(st.dataBar, st.dataBarTextLeft, st.dataBarTextCenter, st.dataBarTextRight, GF.GetScaledBarTextConfig(cfg.dataBar, contentScale), scale)
	GF.ApplyDataBarNameCharLimit(self, st, cfg.dataBar, (def and def.dataBar) or EMPTY, cfg)
	if st.statusText then
		local scfg = cfg.status or {}
		local us = scfg.unitStatus or {}
		local defStatus = def.status or {}
		local defUS = defStatus.unitStatus or {}
		applyStatusTextAnchor(st, us.anchor or defUS.anchor or "CENTER", GF.ScaleOffset(us.offset or defUS.offset or {}, contentScale), scale, layoutAnchor)
	end
	if st.groupNumberText then
		local style = resolveGroupNumberStyle(cfg, def, hc)
		applyStatusTextAnchor(st, style.anchor, style.offset, scale, layoutAnchor, st.groupNumberText)
	end

	if st.health.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
		if st._lastHealthTexture ~= healthTexKey then
			if Pixel and Pixel.SetStatusBarTexture then
				Pixel.SetStatusBarTexture(st.health, UFHelper.resolveTexture(healthTexKey))
			else
				st.health:SetStatusBarTexture(UFHelper.resolveTexture(healthTexKey))
			end
			if UFHelper.configureSpecialTexture then UFHelper.configureSpecialTexture(st.health, "HEALTH", healthTexKey, hc) end
			st._lastHealthTexture = healthTexKey
			stabilizeStatusBarTexture(st.health)
		end
	end
	if st.tempMaxHealthLoss then
		st.tempMaxHealthLoss:SetStatusBarTexture("UI-HUD-UnitFrame-Target-PortraitOn-Bar-TempHPLoss")
		if st.tempMaxHealthLoss.SetStatusBarDesaturated then st.tempMaxHealthLoss:SetStatusBarDesaturated(false) end
		local reverseHealth = hc.reverseFill
		if reverseHealth == nil then reverseHealth = defH.reverseFill == true end
		if UFHelper and UFHelper.applyStatusBarReverseFill then UFHelper.applyStatusBarReverseFill(st.tempMaxHealthLoss, not reverseHealth) end
		st.tempMaxHealthLoss:SetMinMaxValues(0, 1)
		if tempMaxHealthLossEnabled then
			st.tempMaxHealthLoss:Show()
		else
			GF.SetStatusBarValue(st.tempMaxHealthLoss, 0, false, true)
			st.tempMaxHealthLoss:Hide()
		end
		setFrameLevelAbove(st.tempMaxHealthLoss, st.health, 1)
	end
	if st.power.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
		if st._lastPowerTexture ~= powerTexKey then
			if Pixel and Pixel.SetStatusBarTexture then
				Pixel.SetStatusBarTexture(st.power, UFHelper.resolveTexture(powerTexKey))
			else
				st.power:SetStatusBarTexture(UFHelper.resolveTexture(powerTexKey))
			end
			st._lastPowerTexture = powerTexKey
			stabilizeStatusBarTexture(st.power)
		end
	end

	local healthHeight = max(1, (tonumber(h) or 0) - (tonumber(healthBottomOffset) or 0))
	local configuredOverlayFallback = GF._computeOverlayHeightFallback((cfg.height ~= nil and cfg.height) or def.height, (cfg.powerHeight ~= nil and cfg.powerHeight) or def.powerHeight)
	local resolvedAbsorbHeight = GF._resolveRuntimeOverlayHeightSetting(hc.absorbOverlayHeight ~= nil and hc.absorbOverlayHeight or defH.absorbOverlayHeight, configuredOverlayFallback, healthHeight)
	local resolvedHealAbsorbHeight =
		GF._resolveRuntimeOverlayHeightSetting(hc.healAbsorbOverlayHeight ~= nil and hc.healAbsorbOverlayHeight or defH.healAbsorbOverlayHeight, configuredOverlayFallback, healthHeight)
	local absorbAnchorTop = hc.absorbOverlayAnchorTop
	if absorbAnchorTop == nil then absorbAnchorTop = defH.absorbOverlayAnchorTop == true end
	local healAbsorbAnchorTop = hc.healAbsorbOverlayAnchorTop
	if healAbsorbAnchorTop == nil then healAbsorbAnchorTop = defH.healAbsorbOverlayAnchorTop == true end
	local overlayClip = GF._ensureOverlayClipFrame(st.health, "_eqolDirectOverlayClip")
	if st.incomingHeal then
		local incomingHealTextureKey = hc.incomingHealTexture or healthTexKey
		if st.incomingHeal.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
			if st._lastIncomingHealTexture ~= incomingHealTextureKey then
				if Pixel and Pixel.SetStatusBarTexture then
					Pixel.SetStatusBarTexture(st.incomingHeal, UFHelper.resolveTexture(incomingHealTextureKey))
				else
					st.incomingHeal:SetStatusBarTexture(UFHelper.resolveTexture(incomingHealTextureKey))
				end
				if UFHelper.configureSpecialTexture then UFHelper.configureSpecialTexture(st.incomingHeal, "HEALTH", incomingHealTextureKey, hc) end
				st._lastIncomingHealTexture = incomingHealTextureKey
			end
		end
		if st.incomingHeal.SetStatusBarDesaturated then st.incomingHeal:SetStatusBarDesaturated(false) end
		local reverseHealth = hc.reverseFill
		if reverseHealth == nil then reverseHealth = defH.reverseFill == true end
		if UFHelper and UFHelper.setupAbsorbClampReverseAware then
			UFHelper.setupAbsorbClampReverseAware(st.health, st.incomingHeal)
		elseif UFHelper and UFHelper.setupAbsorbClamp then
			UFHelper.setupAbsorbClamp(st.health, st.incomingHeal)
			if UFHelper.applyStatusBarReverseFill then UFHelper.applyStatusBarReverseFill(st.incomingHeal, reverseHealth) end
		end
		if overlayClip and st.incomingHeal.GetParent and st.incomingHeal:GetParent() ~= overlayClip then st.incomingHeal:SetParent(overlayClip) end
		if UFHelper and UFHelper.applyAbsorbClampLayout then
			UFHelper.applyAbsorbClampLayout(st.incomingHeal, st.health, healthHeight, healthHeight, reverseHealth)
		else
			GF._applyOverlayHeight(st.incomingHeal, st.health, healthHeight, healthHeight)
		end
		stabilizeStatusBarTexture(st.incomingHeal)
		setFrameLevelAbove(st.incomingHeal, st.health, 1)
	end
	if st.absorb then
		local absorbTextureKey = hc.absorbTexture or healthTexKey
		if st.absorb.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
			if Pixel and Pixel.SetStatusBarTexture then
				Pixel.SetStatusBarTexture(st.absorb, UFHelper.resolveTexture(absorbTextureKey))
			else
				st.absorb:SetStatusBarTexture(UFHelper.resolveTexture(absorbTextureKey))
			end
			if UFHelper.configureSpecialTexture then UFHelper.configureSpecialTexture(st.absorb, "HEALTH", absorbTextureKey, hc) end
		end
		if st.absorb.SetStatusBarDesaturated then st.absorb:SetStatusBarDesaturated(false) end
		local reverseAbsorb = hc.absorbReverseFill
		if reverseAbsorb == nil then reverseAbsorb = defH.absorbReverseFill == true end
		local absorbDontOverflow = hc.absorbDontOverflowHealthBar
		if absorbDontOverflow == nil then absorbDontOverflow = defH.absorbDontOverflowHealthBar == true end
		absorbDontOverflow = absorbDontOverflow == true and reverseAbsorb == true
		local reverseHealth = hc.reverseFill
		if reverseHealth == nil then reverseHealth = defH.reverseFill == true end
		if UFHelper and UFHelper.applyStatusBarReverseFill then UFHelper.applyStatusBarReverseFill(st.absorb, reverseAbsorb) end
		if reverseAbsorb then
			st.absorb2 = st.absorb2 or CreateFrame("StatusBar", nil, st.health, "BackdropTemplate")
			if st.absorb2.SetStatusBarDesaturated then st.absorb2:SetStatusBarDesaturated(false) end
			st.absorb2:Hide()
		elseif st.absorb2 then
			st.absorb2:Hide()
		end
		stabilizeStatusBarTexture(st.absorb)
		local absorbClip = overlayClip
		if absorbClip and st.absorb.GetParent and st.absorb:GetParent() ~= absorbClip then st.absorb:SetParent(absorbClip) end
		local absorbHeight = resolvedAbsorbHeight
		GF._applyOverlayHeight(st.absorb, absorbClip or st.health, absorbHeight, healthHeight, absorbAnchorTop == true)
		setFrameLevelAbove(st.absorb, st.health, 1)
		if reverseAbsorb and st.absorb2 then
			if st.absorb2.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
				if Pixel and Pixel.SetStatusBarTexture then
					Pixel.SetStatusBarTexture(st.absorb2, UFHelper.resolveTexture(absorbTextureKey))
				else
					st.absorb2:SetStatusBarTexture(UFHelper.resolveTexture(absorbTextureKey))
				end
				if UFHelper.configureSpecialTexture then UFHelper.configureSpecialTexture(st.absorb2, "HEALTH", absorbTextureKey, hc) end
			end
			if st.absorb2.SetStatusBarDesaturated then st.absorb2:SetStatusBarDesaturated(false) end
			if st.absorb2.SetOrientation then st.absorb2:SetOrientation("HORIZONTAL") end
			if UFHelper and UFHelper.applyAbsorbClampLayout then
				if reverseHealth then
					if UFHelper.setupAbsorbClampReverseAware then UFHelper.setupAbsorbClampReverseAware(st.health, st.absorb2) end
				else
					if UFHelper.setupAbsorbClamp then UFHelper.setupAbsorbClamp(st.health, st.absorb2) end
					if not absorbDontOverflow and UFHelper.setupAbsorbOverShift then UFHelper.setupAbsorbOverShift(st.health, st.absorb, absorbHeight, healthHeight, absorbAnchorTop == true) end
				end
				if overlayClip and st.absorb2.GetParent and st.absorb2:GetParent() ~= overlayClip then st.absorb2:SetParent(overlayClip) end
				UFHelper.applyAbsorbClampLayout(st.absorb2, st.health, absorbHeight, healthHeight, reverseHealth, absorbAnchorTop == true)
				syncTextFrameLevels(st)
			end
			stabilizeStatusBarTexture(st.absorb2)
			setFrameLevelAbove(st.absorb2, st.health, 1)
			st.absorb2:SetMinMaxValues(0, 1)
			GF.SetStatusBarValue(st.absorb2, 0, false, true)
			st.absorb2:Hide()
		end
		if st.overAbsorbGlow then
			st.overAbsorbGlow:ClearAllPoints()
			local glowParent = overlayClip or st.health
			if glowParent and st.overAbsorbGlow.GetParent and st.overAbsorbGlow:GetParent() ~= glowParent then st.overAbsorbGlow:SetParent(glowParent) end
			if reverseHealth then
				st.overAbsorbGlow:SetPoint("TOPRIGHT", st.health, "TOPLEFT", 7, 0)
				st.overAbsorbGlow:SetPoint("BOTTOMRIGHT", st.health, "BOTTOMLEFT", 7, 0)
			else
				st.overAbsorbGlow:SetPoint("TOPLEFT", st.health, "TOPRIGHT", -7, 0)
				st.overAbsorbGlow:SetPoint("BOTTOMLEFT", st.health, "BOTTOMRIGHT", -7, 0)
			end
			st.overAbsorbGlow:Hide()
		end
	end
	if st.incomingHeal then setFrameLevelAbove(st.incomingHeal, st.absorb2 or st.absorb or st.health, 1) end
	if st.healAbsorb then
		local healAbsorbTextureKey = hc.healAbsorbTexture or healthTexKey
		if st.healAbsorb.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
			if Pixel and Pixel.SetStatusBarTexture then
				Pixel.SetStatusBarTexture(st.healAbsorb, UFHelper.resolveTexture(healAbsorbTextureKey))
			else
				st.healAbsorb:SetStatusBarTexture(UFHelper.resolveTexture(healAbsorbTextureKey))
			end
			if UFHelper.configureSpecialTexture then UFHelper.configureSpecialTexture(st.healAbsorb, "HEALTH", healAbsorbTextureKey, hc) end
		end
		if st.healAbsorb.SetStatusBarDesaturated then st.healAbsorb:SetStatusBarDesaturated(false) end
		local reverseHealAbsorb = hc.healAbsorbReverseFill
		if reverseHealAbsorb == nil then reverseHealAbsorb = defH.healAbsorbReverseFill == true end
		if UFHelper and UFHelper.applyStatusBarReverseFill then UFHelper.applyStatusBarReverseFill(st.healAbsorb, reverseHealAbsorb) end
		stabilizeStatusBarTexture(st.healAbsorb)
		local healAbsorbClip = overlayClip
		if healAbsorbClip and st.healAbsorb.GetParent and st.healAbsorb:GetParent() ~= healAbsorbClip then st.healAbsorb:SetParent(healAbsorbClip) end
		local healAbsorbHeight = resolvedHealAbsorbHeight
		GF._applyOverlayHeight(st.healAbsorb, healAbsorbClip or st.health, healAbsorbHeight, healthHeight, healAbsorbAnchorTop == true)
		setFrameLevelAbove(st.healAbsorb, st.incomingHeal or st.absorb or st.health, 1)
	end

	local tc = cfg.text or {}
	local rc = cfg.roleIcon or {}
	local sc = cfg.status or {}
	local rolePad = 0
	local roleEnabled = rc.enabled ~= false
	if roleEnabled and type(rc.showRoles) == "table" and not GFH.SelectionHasAny(rc.showRoles) then roleEnabled = false end
	if roleEnabled then
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if not st.roleIcon then
			st.roleIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7)
		end
		if st.roleIcon.GetParent and st.roleIcon:GetParent() ~= indicatorLayer then st.roleIcon:SetParent(indicatorLayer) end
		if st.roleIcon.SetDrawLayer then st.roleIcon:SetDrawLayer("OVERLAY", 7) end
		local size = GF.ScaleContentValue(self, rc.size or 14, cfg, 1)
		local point = rc.point or "LEFT"
		local relPoint = rc.relativePoint or "LEFT"
		local ox = roundToPixel((rc.x or 2) * contentScale, scale)
		local oy = roundToPixel((rc.y or 0) * contentScale, scale)
		local roleAnchor = layoutAnchor or self or st.health
		st.roleIcon:ClearAllPoints()
		if Pixel and Pixel.SetPoint then
			Pixel.SetPoint(st.roleIcon, point, roleAnchor, relPoint, ox, oy)
		else
			st.roleIcon:SetPoint(point, roleAnchor, relPoint, ox, oy)
		end
		if Pixel and Pixel.SetSize then
			Pixel.SetSize(st.roleIcon, size, size, 1, 1)
		else
			st.roleIcon:SetSize(size, size)
		end
		rolePad = size + ((rc.spacing or 2) * contentScale)
	else
		if st.roleIcon then st.roleIcon:Hide() end
	end

	if st.nameText then
		if st.nameText.SetWordWrap then st.nameText:SetWordWrap(false) end
		if st.nameText.SetNonSpaceWrap then st.nameText:SetNonSpaceWrap(false) end
		if st.nameText.SetMaxLines then st.nameText:SetMaxLines(1) end
		local nameFont = tc.font or hc.font
		local nameFontSize = tc.fontSize or hc.fontSize or 12
		local nameFontOutline = tc.fontOutline or hc.fontOutline
		local scaledNameFontSize = GF.ScaleContentValue(self, nameFontSize, cfg, 1)
		if UFHelper and UFHelper.applyFont then UFHelper.applyFont(st.nameText, nameFont, scaledNameFontSize, nameFontOutline) end
		if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.nameText) end
		local nameMetricScale = (st.nameText.GetEffectiveScale and st.nameText:GetEffectiveScale()) or scale
		if not nameMetricScale or nameMetricScale <= 0 then nameMetricScale = scale end
		local nameAnchor = tc.nameAnchor or "LEFT"
		local baseOffset = (cfg.health and cfg.health.offsetLeft) or {}
		if nameAnchor and nameAnchor:find("RIGHT") then
			baseOffset = (cfg.health and cfg.health.offsetRight) or {}
		elseif nameAnchor and not nameAnchor:find("LEFT") then
			baseOffset = (cfg.health and cfg.health.offsetCenter) or {}
		end
		local nameOffset = tc.nameOffset or {}
		local namePad = (nameAnchor == "LEFT") and rolePad or 0
		local nameX = ((nameOffset.x ~= nil and nameOffset.x or baseOffset.x or 6) * contentScale) + namePad
		local nameY = (nameOffset.y ~= nil and nameOffset.y or baseOffset.y or 0) * contentScale
		nameX = roundToPixel(nameX, nameMetricScale)
		nameY = roundToPixel(nameY, nameMetricScale)
		local nameAnchorFrame = layoutAnchor or st.health
		if nameAnchor and nameAnchor:find("BOTTOM") then nameAnchorFrame = st.health or nameAnchorFrame end
		local nameMaxChars = tonumber(tc.nameMaxChars) or 0
		local nameFrameWidth = nameAnchorFrame and nameAnchorFrame.GetWidth and nameAnchorFrame:GetWidth() or 0
		if not nameFrameWidth or nameFrameWidth <= 1 then
			if nameAnchorFrame == st.health then
				nameFrameWidth = max(1, (tonumber(w) or 1) - (tonumber(contentOffsetLeft) or 0) - (tonumber(contentOffsetRight) or 0))
			else
				nameFrameWidth = max(1, (tonumber(w) or 1) + (tonumber(layoutOffsetLeft) or 0) + (tonumber(layoutOffsetRight) or 0))
			end
		end
		nameFrameWidth = roundToPixel(nameFrameWidth, nameMetricScale)
		st.nameText:ClearAllPoints()
		local justifyV = "MIDDLE"
		if nameAnchor and nameAnchor:find("TOP") then
			justifyV = "TOP"
		elseif nameAnchor and nameAnchor:find("BOTTOM") then
			justifyV = "BOTTOM"
		end
		if nameMaxChars <= 0 then
			local vert = justifyV == "MIDDLE" and "CENTER" or justifyV
			local leftPoint = (vert == "CENTER") and "LEFT" or (vert .. "LEFT")
			local rightPad = roundToPixel(4 * contentScale, nameMetricScale)
			local nameWidth = max(1, roundToPixel(nameFrameWidth - nameX - rightPad, nameMetricScale))
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.nameText, leftPoint, nameAnchorFrame, leftPoint, nameX, nameY)
			else
				st.nameText:SetPoint(leftPoint, nameAnchorFrame, leftPoint, roundToPixel(nameX, scale), roundToPixel(nameY, scale))
			end
			GF.SetRegionWidth(st.nameText, nameWidth, scale)
			st._eqolNameTextWidth = nil
		else
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.nameText, nameAnchor, nameAnchorFrame, nameAnchor, nameX, nameY)
			else
				st.nameText:SetPoint(nameAnchor, nameAnchorFrame, nameAnchor, roundToPixel(nameX, scale), roundToPixel(nameY, scale))
			end
		end
		GF.SetRegionHeight(st.nameText, scaledNameFontSize + 2, scale)
		local justify = "CENTER"
		if nameAnchor and nameAnchor:find("LEFT") then
			justify = "LEFT"
		elseif nameAnchor and nameAnchor:find("RIGHT") then
			justify = "RIGHT"
		end
		st.nameText:SetJustifyH(justify)
		if st.nameText.SetJustifyV then st.nameText:SetJustifyV(justifyV) end
		local showName = tc.showName ~= false
		st.nameText:SetShown(showName)
		if not showName then
			st.nameText:SetText("")
			st._lastName = nil
		end
		if UFHelper and UFHelper.applyNameCharLimit then
			local nameCfg = st._nameLimitCfg or {}
			nameCfg.nameMaxChars = tc.nameMaxChars
			nameCfg.font = nameFont
			nameCfg.fontSize = scaledNameFontSize
			nameCfg.fontOutline = nameFontOutline
			st._nameLimitCfg = nameCfg
			if nameMaxChars > 0 then UFHelper.applyNameCharLimit(st, nameCfg, nil) end
		end
	end

	if st.levelText then
		if st.levelText.SetWordWrap then st.levelText:SetWordWrap(false) end
		if st.levelText.SetNonSpaceWrap then st.levelText:SetNonSpaceWrap(false) end
		if st.levelText.SetMaxLines then st.levelText:SetMaxLines(1) end
		local levelFont = sc.levelFont or tc.font or hc.font
		local levelFontSize = sc.levelFontSize or tc.fontSize or hc.fontSize or 12
		local levelOutline = sc.levelFontOutline or tc.fontOutline or hc.fontOutline
		if UFHelper and UFHelper.applyFont then UFHelper.applyFont(st.levelText, levelFont, levelFontSize, levelOutline) end
		local anchor = sc.levelAnchor or "RIGHT"
		local levelOffset = sc.levelOffset or {}
		if st.levelText.SetWidth then
			if Pixel and Pixel.SetWidth then
				Pixel.SetWidth(st.levelText, sc.levelWidth or 26, 1)
			else
				st.levelText:SetWidth(roundToPixel((sc.levelWidth or 26), scale))
			end
		end
		st.levelText:ClearAllPoints()
		if Pixel and Pixel.SetPoint then
			Pixel.SetPoint(st.levelText, anchor, st.health, anchor, levelOffset.x or 0, levelOffset.y or 0)
		else
			st.levelText:SetPoint(anchor, st.health, anchor, roundToPixel(levelOffset.x or 0, scale), roundToPixel(levelOffset.y or 0, scale))
		end
		local justify = "CENTER"
		if anchor and anchor:find("LEFT") then
			justify = "LEFT"
		elseif anchor and anchor:find("RIGHT") then
			justify = "RIGHT"
		end
		st.levelText:SetJustifyH(justify)
	end

	if st.raidIcon then
		local ric = sc.raidIcon or {}
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.raidIcon.GetParent and st.raidIcon:GetParent() ~= indicatorLayer then st.raidIcon:SetParent(indicatorLayer) end
		if st.raidIcon.SetDrawLayer then st.raidIcon:SetDrawLayer("OVERLAY", 7) end
		if ric.enabled ~= false then
			local size = GF.ScaleContentValue(self, ric.size or 18, cfg, 1)
			st.raidIcon:ClearAllPoints()
			local raidPoint = ric.point or "TOP"
			local raidRelativePoint = ric.relativePoint or ric.point or "TOP"
			local raidX = roundToPixel((ric.x or 0) * contentScale, scale)
			local raidY = roundToPixel((ric.y or -2) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.raidIcon, raidPoint, layoutAnchor, raidRelativePoint, raidX, raidY)
			else
				st.raidIcon:SetPoint(raidPoint, layoutAnchor, raidRelativePoint, raidX, raidY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.raidIcon, size, size, 1, 1)
			else
				st.raidIcon:SetSize(size, size)
			end
		else
			st.raidIcon:Hide()
		end
	end

	if st.healthTextLeft or st.healthTextCenter or st.healthTextRight then
		local r, g, b, a
		if hc.useTextClassColor == true and GF:EnsureUnitClassColor(self, st, getUnit(self)) then
			r, g, b, a = st._classR, st._classG, st._classB, st._classA or 1
		else
			r, g, b, a = unpackColor(hc.textColor, defH.textColor or GFH.COLOR_WHITE)
		end
		if st._lastHealthTextR ~= r or st._lastHealthTextG ~= g or st._lastHealthTextB ~= b or st._lastHealthTextA ~= a then
			st._lastHealthTextR, st._lastHealthTextG, st._lastHealthTextB, st._lastHealthTextA = r, g, b, a
			if st.healthTextLeft then st.healthTextLeft:SetTextColor(r, g, b, a) end
			if st.healthTextCenter then st.healthTextCenter:SetTextColor(r, g, b, a) end
			if st.healthTextRight then st.healthTextRight:SetTextColor(r, g, b, a) end
		end
	end

	if st.dataBar then
		local dbR, dbG, dbB, dbA
		if dbc.useClassColor == true and GF:EnsureUnitClassColor(self, st, getUnit(self)) then
			dbR, dbG, dbB, dbA = st._classR, st._classG, st._classB, st._classA or 1
		else
			dbR, dbG, dbB, dbA = unpackColor(dbc.color, defDB.color or { 0.18, 0.18, 0.22, 1 })
		end
		if st._lastDataBarR ~= dbR or st._lastDataBarG ~= dbG or st._lastDataBarB ~= dbB or st._lastDataBarA ~= dbA then
			st._lastDataBarR, st._lastDataBarG, st._lastDataBarB, st._lastDataBarA = dbR, dbG, dbB, dbA
			st.dataBar:SetStatusBarColor(dbR, dbG, dbB, dbA or 1)
		end
		local tr, tg, tb, ta
		if dbc.useTextClassColor == true and GF:EnsureUnitClassColor(self, st, getUnit(self)) then
			tr, tg, tb, ta = st._classR, st._classG, st._classB, st._classA or 1
		else
			tr, tg, tb, ta = unpackColor(dbc.textColor, defDB.textColor or GFH.COLOR_WHITE)
		end
		if st._lastDataBarTextR ~= tr or st._lastDataBarTextG ~= tg or st._lastDataBarTextB ~= tb or st._lastDataBarTextA ~= ta then
			st._lastDataBarTextR, st._lastDataBarTextG, st._lastDataBarTextB, st._lastDataBarTextA = tr, tg, tb, ta
			if st.dataBarTextLeft then st.dataBarTextLeft:SetTextColor(tr, tg, tb, ta) end
			if st.dataBarTextCenter then st.dataBarTextCenter:SetTextColor(tr, tg, tb, ta) end
			if st.dataBarTextRight then st.dataBarTextRight:SetTextColor(tr, tg, tb, ta) end
		end
	end

	if st.leaderIcon then
		local lc = sc.leaderIcon or {}
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.leaderIcon.GetParent and st.leaderIcon:GetParent() ~= indicatorLayer then st.leaderIcon:SetParent(indicatorLayer) end
		if st.leaderIcon.SetDrawLayer then st.leaderIcon:SetDrawLayer("OVERLAY", 7) end
		if lc.enabled ~= false then
			local size = GF.ScaleContentValue(self, lc.size or 12, cfg, 1)
			st.leaderIcon:ClearAllPoints()
			local leaderPoint = lc.point or "TOPLEFT"
			local leaderRelativePoint = lc.relativePoint or "TOPLEFT"
			local leaderX = roundToPixel((lc.x or 0) * contentScale, scale)
			local leaderY = roundToPixel((lc.y or 0) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.leaderIcon, leaderPoint, st.health, leaderRelativePoint, leaderX, leaderY)
			else
				st.leaderIcon:SetPoint(leaderPoint, st.health, leaderRelativePoint, leaderX, leaderY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.leaderIcon, size, size, 1, 1)
			else
				st.leaderIcon:SetSize(size, size)
			end
		else
			st.leaderIcon:Hide()
		end
	end

	if st.assistIcon then
		local acfg = sc.assistIcon or {}
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.assistIcon.GetParent and st.assistIcon:GetParent() ~= indicatorLayer then st.assistIcon:SetParent(indicatorLayer) end
		if st.assistIcon.SetDrawLayer then st.assistIcon:SetDrawLayer("OVERLAY", 7) end
		if acfg.enabled ~= false then
			local size = GF.ScaleContentValue(self, acfg.size or 12, cfg, 1)
			st.assistIcon:ClearAllPoints()
			local assistPoint = acfg.point or "TOPLEFT"
			local assistRelativePoint = acfg.relativePoint or "TOPLEFT"
			local assistX = roundToPixel((acfg.x or 0) * contentScale, scale)
			local assistY = roundToPixel((acfg.y or 0) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.assistIcon, assistPoint, st.health, assistRelativePoint, assistX, assistY)
			else
				st.assistIcon:SetPoint(assistPoint, st.health, assistRelativePoint, assistX, assistY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.assistIcon, size, size, 1, 1)
			else
				st.assistIcon:SetSize(size, size)
			end
		else
			st.assistIcon:Hide()
		end
	end

	if st.raidAssignmentIcon then
		local racfg = sc.raidAssignmentIcon or {}
		local defRaidAssignment = (def.status and def.status.raidAssignmentIcon) or {}
		local raidAssignmentEnabled = racfg.enabled
		if raidAssignmentEnabled == nil then raidAssignmentEnabled = defRaidAssignment.enabled == true end
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.raidAssignmentIcon.GetParent and st.raidAssignmentIcon:GetParent() ~= indicatorLayer then st.raidAssignmentIcon:SetParent(indicatorLayer) end
		if st.raidAssignmentIcon.SetDrawLayer then st.raidAssignmentIcon:SetDrawLayer("OVERLAY", 7) end
		if raidAssignmentEnabled == true then
			local size = GF.ScaleContentValue(self, racfg.size or defRaidAssignment.size or 12, cfg, 1)
			st.raidAssignmentIcon:ClearAllPoints()
			local assignmentPoint = racfg.point or defRaidAssignment.point or "TOPRIGHT"
			local assignmentRelativePoint = racfg.relativePoint or defRaidAssignment.relativePoint or defRaidAssignment.point or "TOPRIGHT"
			local assignmentX = roundToPixel(((racfg.x ~= nil) and racfg.x or (defRaidAssignment.x or -2)) * contentScale, scale)
			local assignmentY = roundToPixel(((racfg.y ~= nil) and racfg.y or (defRaidAssignment.y or -2)) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.raidAssignmentIcon, assignmentPoint, st.health, assignmentRelativePoint, assignmentX, assignmentY)
			else
				st.raidAssignmentIcon:SetPoint(assignmentPoint, st.health, assignmentRelativePoint, assignmentX, assignmentY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.raidAssignmentIcon, size, size, 1, 1)
			else
				st.raidAssignmentIcon:SetSize(size, size)
			end
		else
			st.raidAssignmentIcon:Hide()
		end
	end

	if st.readyCheckIcon then
		local rcfg = GF:GetStatusIconCfg(self, "readyCheckIcon")
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.readyCheckIcon.GetParent and st.readyCheckIcon:GetParent() ~= indicatorLayer then st.readyCheckIcon:SetParent(indicatorLayer) end
		if st.readyCheckIcon.SetDrawLayer then st.readyCheckIcon:SetDrawLayer("OVERLAY", 7) end
		if rcfg.enabled ~= false then
			local size = GF.ScaleContentValue(self, rcfg.size or 16, cfg, 1)
			st.readyCheckIcon:ClearAllPoints()
			local readyPoint = rcfg.point or "CENTER"
			local readyRelativePoint = rcfg.relativePoint or rcfg.point or "CENTER"
			local readyX = roundToPixel((rcfg.x or 0) * contentScale, scale)
			local readyY = roundToPixel((rcfg.y or 0) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.readyCheckIcon, readyPoint, layoutAnchor, readyRelativePoint, readyX, readyY)
			else
				st.readyCheckIcon:SetPoint(readyPoint, layoutAnchor, readyRelativePoint, readyX, readyY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.readyCheckIcon, size, size, 1, 1)
			else
				st.readyCheckIcon:SetSize(size, size)
			end
		else
			st.readyCheckIcon:Hide()
		end
	end

	if st.summonIcon then
		local scfg = GF:GetStatusIconCfg(self, "summonIcon")
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.summonIcon.GetParent and st.summonIcon:GetParent() ~= indicatorLayer then st.summonIcon:SetParent(indicatorLayer) end
		if st.summonIcon.SetDrawLayer then st.summonIcon:SetDrawLayer("OVERLAY", 7) end
		if scfg.enabled ~= false then
			local size = GF.ScaleContentValue(self, scfg.size or 16, cfg, 1)
			st.summonIcon:ClearAllPoints()
			local summonPoint = scfg.point or "CENTER"
			local summonRelativePoint = scfg.relativePoint or scfg.point or "CENTER"
			local summonX = roundToPixel((scfg.x or 0) * contentScale, scale)
			local summonY = roundToPixel((scfg.y or 0) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.summonIcon, summonPoint, layoutAnchor, summonRelativePoint, summonX, summonY)
			else
				st.summonIcon:SetPoint(summonPoint, layoutAnchor, summonRelativePoint, summonX, summonY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.summonIcon, size, size, 1, 1)
			else
				st.summonIcon:SetSize(size, size)
			end
		else
			st.summonIcon:Hide()
		end
	end

	if st.resurrectIcon then
		local rcfg = GF:GetStatusIconCfg(self, "resurrectIcon")
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.resurrectIcon.GetParent and st.resurrectIcon:GetParent() ~= indicatorLayer then st.resurrectIcon:SetParent(indicatorLayer) end
		if st.resurrectIcon.SetDrawLayer then st.resurrectIcon:SetDrawLayer("OVERLAY", 7) end
		if rcfg.enabled ~= false then
			local size = GF.ScaleContentValue(self, rcfg.size or 16, cfg, 1)
			st.resurrectIcon:ClearAllPoints()
			local resurrectPoint = rcfg.point or "CENTER"
			local resurrectRelativePoint = rcfg.relativePoint or rcfg.point or "CENTER"
			local resurrectX = roundToPixel((rcfg.x or 0) * contentScale, scale)
			local resurrectY = roundToPixel((rcfg.y or 0) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.resurrectIcon, resurrectPoint, layoutAnchor, resurrectRelativePoint, resurrectX, resurrectY)
			else
				st.resurrectIcon:SetPoint(resurrectPoint, layoutAnchor, resurrectRelativePoint, resurrectX, resurrectY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.resurrectIcon, size, size, 1, 1)
			else
				st.resurrectIcon:SetSize(size, size)
			end
		else
			st.resurrectIcon:Hide()
		end
	end

	if st.phaseIcon then
		local pcfg = GF:GetStatusIconCfg(self, "phaseIcon")
		local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health
		if st.phaseIcon.GetParent and st.phaseIcon:GetParent() ~= indicatorLayer then st.phaseIcon:SetParent(indicatorLayer) end
		if st.phaseIcon.SetDrawLayer then st.phaseIcon:SetDrawLayer("OVERLAY", 7) end
		if pcfg.enabled ~= false then
			local size = GF.ScaleContentValue(self, pcfg.size or 14, cfg, 1)
			st.phaseIcon:ClearAllPoints()
			local phasePoint = pcfg.point or "TOPLEFT"
			local phaseRelativePoint = pcfg.relativePoint or pcfg.point or "TOPLEFT"
			local phaseX = roundToPixel((pcfg.x or 0) * contentScale, scale)
			local phaseY = roundToPixel((pcfg.y or 0) * contentScale, scale)
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(st.phaseIcon, phasePoint, layoutAnchor, phaseRelativePoint, phaseX, phaseY)
			else
				st.phaseIcon:SetPoint(phasePoint, layoutAnchor, phaseRelativePoint, phaseX, phaseY)
			end
			if Pixel and Pixel.SetSize then
				Pixel.SetSize(st.phaseIcon, size, size, 1, 1)
			else
				st.phaseIcon:SetSize(size, size)
			end
		else
			st.phaseIcon:Hide()
		end
	end

	local baseLevel = (st.barGroup:GetFrameLevel() or 0)
	st.health:SetFrameLevel(GF.ClampFrameLevel(baseLevel + 1))
	if not powerDetached then st.power:SetFrameLevel(GF.ClampFrameLevel(baseLevel + 1)) end
	syncTextFrameLevels(st)

	st._lastHealthPx = nil
	st._lastHealthBarW = nil
	st._lastPowerPx = nil
	st._lastPowerBarW = nil

	GF:UpdatePortrait(self)
	if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.LayoutButton then UF.GroupFramesHealerBuffs.LayoutButton(self) end
	GF:UpdateHighlightState(self)
	GF:UpdatePrivateAuras(self)
end

local GROW_DIRS = { "UP", "DOWN", "LEFT", "RIGHT" }
GF._oppositeAuraAnchorPoints = {
	TOPLEFT = "BOTTOMRIGHT",
	TOP = "BOTTOM",
	TOPRIGHT = "BOTTOMLEFT",
	LEFT = "RIGHT",
	CENTER = "CENTER",
	RIGHT = "LEFT",
	BOTTOMLEFT = "TOPRIGHT",
	BOTTOM = "TOP",
	BOTTOMRIGHT = "TOPLEFT",
}

function GF.GetAuraGridBasePoint(primary, secondary)
	local primaryHorizontal = primary == "LEFT" or primary == "RIGHT"
	local horizontalDir = primaryHorizontal and primary or secondary
	local verticalDir = primaryHorizontal and secondary or primary
	local xSign = (horizontalDir == "RIGHT") and 1 or -1
	local ySign = (verticalDir == "UP") and 1 or -1
	return (ySign == 1 and "BOTTOM" or "TOP") .. (xSign == 1 and "LEFT" or "RIGHT")
end

local function parseAuraGrowth(growth)
	if not growth or growth == "" then return end
	local raw = tostring(growth):upper()
	local first, second = raw:match("^(%a+)[_%s]+(%a+)$")
	if not first then
		for i = 1, #GROW_DIRS do
			local dir = GROW_DIRS[i]
			if raw:sub(1, #dir) == dir then
				local rest = raw:sub(#dir + 1)
				if rest == "UP" or rest == "DOWN" or rest == "LEFT" or rest == "RIGHT" then
					first, second = dir, rest
					break
				end
			end
		end
	end
	if not first or not second then return end
	local firstVertical = first == "UP" or first == "DOWN"
	local secondVertical = second == "UP" or second == "DOWN"
	if firstVertical == secondVertical then return end
	return first, second
end

local function resolveAuraGrowth(anchorPoint, growth, growthX, growthY)
	local anchor = (anchorPoint or "TOPLEFT"):upper()
	local primary, secondary = parseAuraGrowth(growth)
	if not primary and growthX and growthY then
		local gx = tostring(growthX):upper()
		local gy = tostring(growthY):upper()
		local gxVert = gx == "UP" or gx == "DOWN"
		local gyVert = gy == "UP" or gy == "DOWN"
		if gxVert ~= gyVert then
			primary, secondary = gx, gy
		end
	end
	if not primary then
		local fallback
		if anchor:find("TOP", 1, true) then
			fallback = "RIGHTUP"
		elseif anchor:find("LEFT", 1, true) then
			fallback = "LEFTDOWN"
		else
			fallback = "RIGHTDOWN"
		end
		primary, secondary = parseAuraGrowth(fallback)
	end
	return anchor, primary, secondary
end

function GF.ResolveAuraContainerBoundaryPoint(anchorPoint, anchorOutside, primary, secondary, owner)
	local anchor = tostring(anchorPoint or "TOPLEFT"):upper()
	if anchorOutside ~= true then return anchor end
	if owner and owner._eqolGroupKind == "party" and primary and secondary then
		if anchor == "TOPLEFT" or anchor == "TOPRIGHT" or anchor == "BOTTOMLEFT" or anchor == "BOTTOMRIGHT" then return GF.GetAuraGridBasePoint(primary, secondary) end
	end
	return (GF._oppositeAuraAnchorPoints and GF._oppositeAuraAnchorPoints[anchor]) or anchor
end

local function growthPairToString(primary, secondary)
	if not primary or not secondary then return nil end
	return tostring(primary):upper() .. tostring(secondary):upper()
end

local function getAuraGrowthValue(typeCfg, anchorPoint)
	if typeCfg and typeCfg.growth and typeCfg.growth ~= "" then
		local primary, secondary = parseAuraGrowth(typeCfg.growth)
		if primary then return growthPairToString(primary, secondary) end
	end
	local _, primary, secondary = resolveAuraGrowth(anchorPoint, nil, typeCfg and typeCfg.growthX, typeCfg and typeCfg.growthY)
	return growthPairToString(primary, secondary) or "RIGHTDOWN"
end

local function applyAuraGrowth(typeCfg, value)
	if not typeCfg then return end
	if value == nil or value == "" then
		typeCfg.growth = nil
		return
	end
	local primary, secondary = parseAuraGrowth(value)
	if not primary then return end
	typeCfg.growth = tostring(value):upper()
	local primaryHorizontal = primary == "LEFT" or primary == "RIGHT"
	local horizontalDir = primaryHorizontal and primary or secondary
	local verticalDir = primaryHorizontal and secondary or primary
	typeCfg.growthX = horizontalDir
	typeCfg.growthY = verticalDir
end

local function ensureAuraContainer(st, key)
	if not st then return nil end
	local parent = GF.GetLayoutAnchorFrame(st, st.barGroup or st.frame)
	if not st[key] then
		st[key] = CreateFrame("Frame", nil, parent)
		st[key]:EnableMouse(false)
	end
	if st[key].GetParent and parent and st[key]:GetParent() ~= parent then st[key]:SetParent(parent) end
	local base = st.statusIconLayer or st.healthTextLayer or parent or st.barGroup or st.frame or st[key]:GetParent()
	if base then
		if st[key].SetFrameStrata and base.GetFrameStrata then st[key]:SetFrameStrata(base:GetFrameStrata()) end
		if st[key].SetFrameLevel and base.GetFrameLevel then st[key]:SetFrameLevel(GF.ClampFrameLevel((base:GetFrameLevel() or 0) + 10)) end
	end
	return st[key]
end

GF.EXTERNAL_GLOW_KEY = GF.EXTERNAL_GLOW_KEY or "EQOL_EXTERNAL"
GF.EXTERNAL_GLOW_STYLE_OPTIONS = GF.EXTERNAL_GLOW_STYLE_OPTIONS
	or {
		{ value = "MARCHING_ANTS", label = "Marching ants" },
		{ value = "FLASH", label = "Flash" },
		{ value = "BLIZZARD", label = "Blizzard" },
		{ value = "PULSING", label = "Pulsing" },
	}

function GF.NormalizeExternalGlowStyle(style, fallback)
	local normalized = type(style) == "string" and string.upper(style) or nil
	if normalized == "BLIZZARD" then return "BLIZZARD" end
	if normalized == "FLASH" then return "FLASH" end
	if normalized == "MARCHING_ANTS" or normalized == "MARCHINGANTS" or normalized == "ANTS" then return "MARCHING_ANTS" end
	if normalized == "PULSING" or normalized == "PULSE" then return "PULSING" end

	normalized = type(fallback) == "string" and string.upper(fallback) or nil
	if normalized == "BLIZZARD" then return "BLIZZARD" end
	if normalized == "FLASH" then return "FLASH" end
	if normalized == "MARCHING_ANTS" or normalized == "MARCHINGANTS" or normalized == "ANTS" then return "MARCHING_ANTS" end
	if normalized == "PULSING" or normalized == "PULSE" then return "PULSING" end
	return "MARCHING_ANTS"
end

function GF.NormalizeExternalGlowStyleForIconShape(style, fallback, shape)
	shape = GF.NormalizeAuraIconShape(shape, "DEFAULT")
	if not GF.IsAuraIconShapeBackdropCompatible(shape) then return "PULSING" end
	return GF.NormalizeExternalGlowStyle(style, fallback)
end

function GF.GetExternalGlowStyleOptions(shape)
	if not GF.IsAuraIconShapeBackdropCompatible(shape) then
		for _, option in ipairs(GF.EXTERNAL_GLOW_STYLE_OPTIONS) do
			if option.value == "PULSING" then return { option } end
		end
	end
	return GF.EXTERNAL_GLOW_STYLE_OPTIONS
end

function GF.NormalizeExternalGlowInset(value, fallback) return clampNumber(value, -20, 20, fallback or 0) end

function GF.GetExternalGlowColor(typeCfg, defaultCfg)
	typeCfg = typeCfg or EMPTY
	defaultCfg = defaultCfg or EMPTY
	return typeCfg.glowColor or typeCfg.borderColor or defaultCfg.glowColor or defaultCfg.borderColor or { 1, 0.25, 0.25, 1 }
end

function GF.StopExternalGlow(btn)
	if not btn then return end
	if btn._eqolExternalGlowManaged and addon.Glow and addon.Glow.Stop then addon.Glow.Stop(btn, GF.EXTERNAL_GLOW_KEY, true) end
	btn._eqolExternalGlowManaged = nil
	btn._eqolExternalGlowState = nil
end

function GF.ApplyExternalGlow(btn, style)
	if not btn then return end
	local Glow = addon.Glow
	if not (Glow and Glow.Start and Glow.Stop) then return end
	if not (style and style.externalGlowEnabled == true) then
		GF.StopExternalGlow(btn)
		return
	end

	local glowStyle = GF.NormalizeExternalGlowStyleForIconShape(style.externalGlowStyle, "MARCHING_ANTS", style.iconShape)
	local glowInset = GF.NormalizeExternalGlowInset(style.externalGlowInset, 0)
	local r, g, b, a = unpackColor(style.externalGlowColor, { 1, 0.25, 0.25, 1 })
	local shape = GF.NormalizeAuraIconShape(style.iconShape, "DEFAULT")
	local state = btn._eqolExternalGlowState
	if state and state.style == glowStyle and state.inset == glowInset and state.r == r and state.g == g and state.b == b and state.a == a and state.shape == shape then return end

	Glow.Start(btn, GF.EXTERNAL_GLOW_KEY, glowStyle, {
		color = { r, g, b, a },
		cooldown = btn.cd,
		inset = glowInset,
		shape = shape,
		hostFrameLevelOffset = 8,
		frameLevel = 8,
	})

	state = state or {}
	state.style = glowStyle
	state.inset = glowInset
	state.shape = shape
	state.r = r
	state.g = g
	state.b = b
	state.a = a
	btn._eqolExternalGlowManaged = true
	btn._eqolExternalGlowState = state
end

local function hideAuraButtons(buttons, startIndex)
	if not buttons then return end
	for i = startIndex, #buttons do
		local btn = buttons[i]
		if btn then
			btn._showTooltip = false
			btn._tooltipUseEditMode = nil
			btn._tooltipAnchor = nil
			if btn.SetMouseClickEnabled and btn._eqolAuraMouseClickEnabled ~= false then
				btn:SetMouseClickEnabled(false)
				btn._eqolAuraMouseClickEnabled = false
			end
			if btn.SetMouseMotionEnabled and btn._eqolAuraMouseMotionEnabled ~= false then
				btn:SetMouseMotionEnabled(false)
				btn._eqolAuraMouseMotionEnabled = false
			end
			if btn.EnableMouse then
				if btn._eqolAuraMouseEnabled ~= false then btn:EnableMouse(false) end
				btn._eqolAuraMouseEnabled = false
			end
			if GameTooltip and GameTooltip.IsOwned and GameTooltip.Hide and GameTooltip:IsOwned(btn) then GameTooltip:Hide() end
			if btn._eqolExternalGlowManaged then GF.StopExternalGlow(btn) end
			btn:Hide()
		end
	end
end

local function setAuraTooltipState(btn, style)
	if not (btn and style) then return end
	local show = style.showTooltip == true
	btn._tooltipUseEditMode = style.tooltipUseEditMode == true
	btn._tooltipAnchor = style.tooltipAnchor or "ANCHOR_BOTTOMRIGHT"
	if btn._showTooltip ~= show then btn._showTooltip = show end
	if btn.SetMouseMotionEnabled and btn._eqolAuraMouseMotionEnabled ~= show then
		btn:SetMouseMotionEnabled(show)
		btn._eqolAuraMouseMotionEnabled = show
	end
	if btn.EnableMouse then
		if btn._eqolAuraMouseEnabled ~= show then
			btn:EnableMouse(show)
			btn._eqolAuraMouseEnabled = show
			if btn.SetMouseClickEnabled then btn:SetMouseClickEnabled(false) end
		end
	end
	if not show and GameTooltip and GameTooltip.IsOwned and GameTooltip.Hide and GameTooltip:IsOwned(btn) then GameTooltip:Hide() end
end

local function calcAuraGridSize(shown, perRow, size, spacing, primary)
	if shown == nil or shown < 1 then return 0.001, 0.001 end
	perRow = perRow or 1
	if perRow < 1 then perRow = 1 end
	size = size or 16
	spacing = spacing or 0
	local primaryVertical = primary == "UP" or primary == "DOWN"
	local rows, cols
	if primaryVertical then
		rows = math.min(shown, perRow)
		cols = math.ceil(shown / perRow)
	else
		rows = math.ceil(shown / perRow)
		cols = math.min(shown, perRow)
	end
	if rows < 1 then rows = 1 end
	if cols < 1 then cols = 1 end
	local w = cols * size + spacing * max(0, cols - 1)
	local h = rows * size + spacing * max(0, rows - 1)
	if w <= 0 then w = 0.001 end
	if h <= 0 then h = 0.001 end
	return w, h
end

local function updateAuraContainerSize(container, anchorPoint, shown, maxCount, perRow, size, spacing, primary)
	if not container then return end
	shown = tonumber(shown) or 0
	maxCount = tonumber(maxCount) or shown
	local maxW, maxH = calcAuraGridSize(maxCount, perRow, size, spacing, primary)
	local shownW, shownH = calcAuraGridSize(shown, perRow, size, spacing, primary)
	local anchor = tostring(anchorPoint or "TOPLEFT"):upper()
	local centerX = not anchor:find("LEFT", 1, true) and not anchor:find("RIGHT", 1, true)
	local centerY = not anchor:find("TOP", 1, true) and not anchor:find("BOTTOM", 1, true)
	local scale = GFH.GetEffectiveScale(container)
	local w = centerX and shownW or maxW
	local h = centerY and shownH or maxH
	if centerX then
		w = roundToEvenPixel(w, scale)
	else
		w = roundToPixel(w, scale)
	end
	if centerY then
		h = roundToEvenPixel(h, scale)
	else
		h = roundToPixel(h, scale)
	end
	if w <= 0 then w = 0.001 end
	if h <= 0 then h = 0.001 end
	if container._eqolAuraLayoutW == w and container._eqolAuraLayoutH == h then return end
	if Pixel and Pixel.SetSize then
		Pixel.SetSize(container, w, h)
	else
		container:SetSize(w, h)
	end
	container._eqolAuraLayoutW = w
	container._eqolAuraLayoutH = h
end

local function positionAuraButton(btn, container, primary, secondary, index, perRow, size, spacing)
	if not (btn and container) then return end
	perRow = perRow or 1
	if perRow < 1 then perRow = 1 end
	local primaryHorizontal = primary == "LEFT" or primary == "RIGHT"
	local row, col
	if primaryHorizontal then
		row = math.floor((index - 1) / perRow)
		col = (index - 1) % perRow
	else
		row = (index - 1) % perRow
		col = math.floor((index - 1) / perRow)
	end
	local horizontalDir = primaryHorizontal and primary or secondary
	local verticalDir = primaryHorizontal and secondary or primary
	local xSign = (horizontalDir == "RIGHT") and 1 or -1
	local ySign = (verticalDir == "UP") and 1 or -1
	local basePoint = GF.GetAuraGridBasePoint(primary, secondary)
	local scale = GFH.GetEffectiveScale(container)
	local step = size + spacing
	local x = roundToPixel(col * step * xSign, scale)
	local y = roundToPixel(row * step * ySign, scale)
	btn:ClearAllPoints()
	if Pixel and Pixel.SetPoint then
		Pixel.SetPoint(btn, basePoint, container, basePoint, x, y)
	else
		btn:SetPoint(basePoint, container, basePoint, x, y)
	end
end

local function resolveRoleAtlas(roleKey, style)
	if roleKey == "NONE" then return nil end
	if style == "FRAME" then
		if roleKey == "TANK" then return "UI-Frame-TankIcon", "UI-LFG-RoleIcon-Tank" end
		if roleKey == "HEALER" then return "UI-Frame-HealerIcon", "UI-LFG-RoleIcon-Healer" end
		if roleKey == "DAMAGER" then return "UI-Frame-DpsIcon", "UI-LFG-RoleIcon-DPS" end
	end
	if style == "CIRCLE" then
		if GetMicroIconForRole then return GetMicroIconForRole(roleKey) end
		if roleKey == "TANK" then return "UI-LFG-RoleIcon-Tank-Micro-GroupFinder" end
		if roleKey == "HEALER" then return "UI-LFG-RoleIcon-Healer-Micro-GroupFinder" end
		if roleKey == "DAMAGER" then return "UI-LFG-RoleIcon-DPS-Micro-GroupFinder" end
	end
	if roleKey == "TANK" then return "roleicon-tiny-tank" end
	if roleKey == "HEALER" then return "roleicon-tiny-healer" end
	if roleKey == "DAMAGER" then return "roleicon-tiny-dps" end
	return nil
end

function GF:GetStatusIconCfg(self, key)
	local kind = (self and self._eqolGroupKind) or "party"
	local cfg = (self and self._eqolCfg) or getCfg(kind)
	return GFH.GetStatusIconCfg(kind, cfg, DEFAULTS, key)
end

function GF:UpdateRoleIcon(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st) then return end
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local rc = cfg and cfg.roleIcon or {}
	if rc.enabled == false then
		if st.roleIcon then st.roleIcon:Hide() end
		return
	end
	local indicatorLayer = st.statusIconLayer or st.healthTextLayer or st.health or st.barGroup or st.frame
	if not st.roleIcon then st.roleIcon = (Pixel and Pixel.CreateTexture and Pixel.CreateTexture(indicatorLayer, nil, "OVERLAY", nil, 7)) or indicatorLayer:CreateTexture(nil, "OVERLAY", nil, 7) end
	if st.roleIcon.GetParent and st.roleIcon:GetParent() ~= indicatorLayer then st.roleIcon:SetParent(indicatorLayer) end
	if st.roleIcon.SetDrawLayer then st.roleIcon:SetDrawLayer("OVERLAY", 7) end
	local roleKey = getUnitRoleKey(unit)
	if isEditModeActive() and st._previewRole then roleKey = st._previewRole end
	if roleKey == "NONE" and isEditModeActive() then roleKey = "DAMAGER" end
	local selection = rc.showRoles
	if type(selection) == "table" then
		if not GFH.SelectionHasAny(selection) then
			st._lastRoleAtlas = nil
			st._lastRoleFallbackAtlas = nil
			st.roleIcon:Hide()
			return
		end
		if roleKey == "NONE" or not GFH.SelectionContains(selection, roleKey) then
			st._lastRoleAtlas = nil
			st._lastRoleFallbackAtlas = nil
			st.roleIcon:Hide()
			return
		end
	end
	local style = rc.style or "TINY"
	local atlas, fallbackAtlas = resolveRoleAtlas(roleKey, style)
	if atlas then
		if st._lastRoleAtlas ~= atlas or st._lastRoleFallbackAtlas ~= fallbackAtlas then
			st._lastRoleAtlas = atlas
			st._lastRoleFallbackAtlas = fallbackAtlas
			local ok
			if Pixel and Pixel.SetAtlas then
				ok = Pixel.SetAtlas(st.roleIcon, atlas, false)
				if ok ~= true and fallbackAtlas then Pixel.SetAtlas(st.roleIcon, fallbackAtlas, false) end
			else
				ok = st.roleIcon:SetAtlas(atlas, false)
				if ok ~= true and fallbackAtlas then st.roleIcon:SetAtlas(fallbackAtlas, false) end
				if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.roleIcon) end
			end
		end
		st.roleIcon:Show()
	else
		st._lastRoleAtlas = nil
		st._lastRoleFallbackAtlas = nil
		st.roleIcon:Hide()
	end
end

function GF:UpdateRaidIcon(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st and st.raidIcon) then return end
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local sc = cfg and cfg.status or {}
	local rcfg = sc.raidIcon or {}
	if rcfg.enabled == false then
		st.raidIcon:Hide()
		return
	end
	if isEditModeActive() then
		if SetRaidTargetIconTexture then SetRaidTargetIconTexture(st.raidIcon, 8) end
		if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.raidIcon) end
		st.raidIcon:Show()
		return
	end
	local idx = GetRaidTargetIndex and GetRaidTargetIndex(unit)
	if idx then
		if SetRaidTargetIconTexture then SetRaidTargetIconTexture(st.raidIcon, idx) end
		if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.raidIcon) end
		st.raidIcon:Show()
	else
		st.raidIcon:Hide()
	end
end

function GF.GetUnitRaidAssignmentRole(unit)
	if not (unit and UnitInRaid and UnitInRaid(unit)) then return nil end
	if GetPartyAssignment then
		if GetPartyAssignment("MAINTANK", unit) then return "MAINTANK" end
		if GetPartyAssignment("MAINASSIST", unit) then return "MAINASSIST" end
	end
	if not GetRaidRosterInfo then return nil end
	local raidID = UnitInRaid(unit)
	if not raidID then return nil end
	local role = select(10, GetRaidRosterInfo(raidID))
	if role == "MAINTANK" or role == "MAINASSIST" then return role end
	return nil
end

function GF:UpdateGroupIcons(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (st and st.leaderIcon and st.assistIcon and st.raidAssignmentIcon) then return end
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local scfg = cfg and cfg.status or {}

	local lc = scfg.leaderIcon or {}
	if lc.enabled == false then
		st.leaderIcon:Hide()
	else
		local showLeader = unit and UnitIsGroupLeader and UnitIsGroupLeader(unit)
		if not showLeader and isEditModeActive() then showLeader = true end
		if showLeader then
			if Pixel and Pixel.SetAtlas then
				Pixel.SetAtlas(st.leaderIcon, "UI-HUD-UnitFrame-Player-Group-LeaderIcon", false)
			else
				st.leaderIcon:SetAtlas("UI-HUD-UnitFrame-Player-Group-LeaderIcon", false)
				if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.leaderIcon) end
			end
			st.leaderIcon:Show()
		else
			st.leaderIcon:Hide()
		end
	end

	local racfg = scfg.raidAssignmentIcon or {}
	local defRaidAssignment = (DEFAULTS[self._eqolGroupKind or "party"] and DEFAULTS[self._eqolGroupKind or "party"].status and DEFAULTS[self._eqolGroupKind or "party"].status.raidAssignmentIcon)
		or {}
	local raidAssignmentEnabled = racfg.enabled
	if raidAssignmentEnabled == nil then raidAssignmentEnabled = defRaidAssignment.enabled == true end
	if self._eqolGroupKind ~= "raid" or raidAssignmentEnabled ~= true then
		st.raidAssignmentIcon:Hide()
	else
		local raidAssignment = GF.GetUnitRaidAssignmentRole(unit)
		local showRaidAssignment = raidAssignment ~= nil
		if not showRaidAssignment and isEditModeActive() then
			raidAssignment = "MAINTANK"
			showRaidAssignment = true
		end
		if showRaidAssignment then
			local atlas = (raidAssignment == "MAINTANK") and "RaidFrame-Icon-MainTank" or "RaidFrame-Icon-MainAssist"
			local fallbackTexture = (raidAssignment == "MAINTANK") and "Interface\\GroupFrame\\UI-Group-MainTankIcon" or "Interface\\GroupFrame\\UI-Group-MainAssistIcon"
			if Pixel and Pixel.SetAtlas then
				local ok = Pixel.SetAtlas(st.raidAssignmentIcon, atlas, false)
				if ok ~= true and Pixel.SetTexture then Pixel.SetTexture(st.raidAssignmentIcon, fallbackTexture) end
			else
				local ok = st.raidAssignmentIcon:SetAtlas(atlas, false)
				if ok ~= true then st.raidAssignmentIcon:SetTexture(fallbackTexture) end
			end
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.raidAssignmentIcon) end
			st.raidAssignmentIcon:Show()
		else
			st.raidAssignmentIcon:Hide()
		end
	end

	local acfg = scfg.assistIcon or {}
	if self._eqolGroupKind == "party" or acfg.enabled == false then
		st.assistIcon:Hide()
	else
		local isAssistant = unit and UnitIsGroupAssistant and UnitIsGroupAssistant(unit)
		local showAssist = isAssistant
		if not showAssist and isEditModeActive() then showAssist = true end
		if showAssist then
			if Pixel and Pixel.SetTexture then
				Pixel.SetTexture(st.assistIcon, "Interface\\GroupFrame\\UI-Group-AssistantIcon")
			else
				st.assistIcon:SetTexture("Interface\\GroupFrame\\UI-Group-AssistantIcon")
			end
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.assistIcon) end
			st.assistIcon:Show()
		else
			st.assistIcon:Hide()
		end
	end
end

function GF:UpdateReadyCheckIcon(self, event)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st and st.readyCheckIcon) then return end

	local rcfg = GF:GetStatusIconCfg(self, "readyCheckIcon")
	if rcfg.enabled == false then
		GFH.CancelReadyCheckIconTimer(st)
		st._lastReadyCheckStatus = nil
		st.readyCheckIcon:Hide()
		return
	end

	if isEditModeActive() and st._preview then
		GFH.CancelReadyCheckIconTimer(st)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.readyCheckIcon, GFH.STATUS_ICON_CONST.waiting)
		else
			st.readyCheckIcon:SetTexture(GFH.STATUS_ICON_CONST.waiting)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.readyCheckIcon) end
		end
		st.readyCheckIcon:Show()
		return
	end

	local sampleActive = rcfg.sample == true and isEditModeActive()
	local status = GetReadyCheckStatus and GetReadyCheckStatus(unit) or nil
	if status == "ready" then
		GFH.CancelReadyCheckIconTimer(st)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.readyCheckIcon, GFH.STATUS_ICON_CONST.ready)
		else
			st.readyCheckIcon:SetTexture(GFH.STATUS_ICON_CONST.ready)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.readyCheckIcon) end
		end
		st.readyCheckIcon:Show()
	elseif status == "notready" then
		GFH.CancelReadyCheckIconTimer(st)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.readyCheckIcon, GFH.STATUS_ICON_CONST.notReady)
		else
			st.readyCheckIcon:SetTexture(GFH.STATUS_ICON_CONST.notReady)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.readyCheckIcon) end
		end
		st.readyCheckIcon:Show()
	elseif status == "waiting" then
		GFH.CancelReadyCheckIconTimer(st)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.readyCheckIcon, GFH.STATUS_ICON_CONST.waiting)
		else
			st.readyCheckIcon:SetTexture(GFH.STATUS_ICON_CONST.waiting)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.readyCheckIcon) end
		end
		st.readyCheckIcon:Show()
	elseif sampleActive then
		GFH.CancelReadyCheckIconTimer(st)
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.readyCheckIcon, GFH.STATUS_ICON_CONST.waiting)
		else
			st.readyCheckIcon:SetTexture(GFH.STATUS_ICON_CONST.waiting)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.readyCheckIcon) end
		end
		st.readyCheckIcon:Show()
	elseif event ~= "READY_CHECK_FINISHED" then
		GFH.CancelReadyCheckIconTimer(st)
		st.readyCheckIcon:Hide()
	end

	if event == "READY_CHECK_FINISHED" and st.readyCheckIcon:IsShown() and not sampleActive then GFH.ScheduleReadyCheckIconHide(st, st.readyCheckIcon, 6) end
	st._lastReadyCheckStatus = status
end

function GF:UpdateSummonIcon(self)
	local st = getState(self)
	if not (st and st.summonIcon) then return end

	local function setSummonVisual(texturePath)
		if st._lastSummonAtlas ~= texturePath then
			st._lastSummonAtlas = texturePath
			if Pixel and Pixel.SetTexture then
				Pixel.SetTexture(st.summonIcon, texturePath)
			else
				st.summonIcon:SetTexture(texturePath)
			end
			if st.summonIcon.SetTexCoord then st.summonIcon:SetTexCoord(0, 1, 0, 1) end
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.summonIcon) end
		end
	end

	local scfg = GF:GetStatusIconCfg(self, "summonIcon")
	if scfg.enabled == false then
		st._summonActiveReal = false
		st.summonIcon:Hide()
		return
	end

	local sampleActive = scfg.sample == true and isEditModeActive()
	if sampleActive then
		st._summonActiveReal = false
		setSummonVisual(GFH.STATUS_ICON_CONST.summonPending)
		st.summonIcon:Show()
		return
	end

	local unit = getUnit(self)
	if not unit then
		st._lastSummonAtlas = nil
		st._summonActiveReal = false
		st.summonIcon:Hide()
		return
	end
	local summonStatus = (C_IncomingSummon and C_IncomingSummon.IncomingSummonStatus) and C_IncomingSummon.IncomingSummonStatus(unit) or GFH.STATUS_ICON_CONST.summonStatusNone
	local texture
	if summonStatus == GFH.STATUS_ICON_CONST.summonStatusPending then
		texture = GFH.STATUS_ICON_CONST.summonPending
	elseif summonStatus == GFH.STATUS_ICON_CONST.summonStatusAccepted then
		texture = GFH.STATUS_ICON_CONST.summonAccepted
	elseif summonStatus == GFH.STATUS_ICON_CONST.summonStatusDeclined then
		texture = GFH.STATUS_ICON_CONST.summonDeclined
	end

	if texture then
		st._summonActiveReal = true
		setSummonVisual(texture)
		st.summonIcon:Show()
	else
		st._lastSummonAtlas = nil
		st._summonActiveReal = false
		st.summonIcon:Hide()
	end
end

function GF:UpdateResurrectIcon(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st and st.resurrectIcon) then return end

	local rcfg = GF:GetStatusIconCfg(self, "resurrectIcon")
	if rcfg.enabled == false then
		st.resurrectIcon:Hide()
		return
	end

	if st._summonActiveReal then
		st.resurrectIcon:Hide()
		return
	end

	local showResurrect = UnitHasIncomingResurrection and UnitHasIncomingResurrection(unit)
	if not showResurrect and isEditModeActive() and st._preview then showResurrect = true end
	if not showResurrect and rcfg.sample == true and isEditModeActive() then showResurrect = true end

	if showResurrect then
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.resurrectIcon, GFH.STATUS_ICON_CONST.resurrect)
		else
			st.resurrectIcon:SetTexture(GFH.STATUS_ICON_CONST.resurrect)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.resurrectIcon) end
		end
		st.resurrectIcon:Show()
	else
		st.resurrectIcon:Hide()
	end
end

function GF:UpdatePhaseIcon(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st and st.phaseIcon) then return end

	local pcfg = GF:GetStatusIconCfg(self, "phaseIcon")
	if pcfg.enabled == false then
		st._phaseReason = nil
		st.phaseIcon:Hide()
		return
	end

	local reason = (UnitIsPlayer and UnitIsPlayer(unit) and UnitIsConnected and UnitIsConnected(unit) and UnitPhaseReason) and UnitPhaseReason(unit) or nil
	if not reason and isEditModeActive() and st._preview then reason = 1 end
	if not reason and pcfg.sample == true and isEditModeActive() then reason = 1 end
	st._phaseReason = reason

	if reason then
		if Pixel and Pixel.SetTexture then
			Pixel.SetTexture(st.phaseIcon, GFH.STATUS_ICON_CONST.phase)
		else
			st.phaseIcon:SetTexture(GFH.STATUS_ICON_CONST.phase)
			if Pixel and Pixel.DisableSnap then Pixel.DisableSnap(st.phaseIcon) end
		end
		st.phaseIcon:Show()
	else
		st.phaseIcon:Hide()
	end
end

function GF:UpdateStatusIcons(self, event)
	GF:UpdateReadyCheckIcon(self, event)
	GF:UpdateSummonIcon(self)
	GF:UpdateResurrectIcon(self)
	GF:UpdatePhaseIcon(self)
end

function GF:UpdateHighlightState(self)
	if not self then return end
	local st = getState(self)
	if not st then return end
	local frames = st._highlightFrames
	local hoverFrame = frames and frames.hover
	local aggroFrame = frames and frames.aggro
	local targetFrame = frames and frames.target
	local unit = getUnit(self)
	if not unit then
		if hoverFrame then hoverFrame:Hide() end
		if aggroFrame then aggroFrame:Hide() end
		if targetFrame then targetFrame:Hide() end
		return
	end

	local targetCfg = st._highlightTargetCfg
	local hoverCfg = st._highlightHoverCfg
	local aggroCfg = st._highlightAggroCfg
	local inEditMode = isEditModeActive()
	local previewIndex = st._previewIndex or self._eqolPreviewIndex or 0
	local isTarget = UnitIsUnit and UnitIsUnit(unit, "target")
	local showTarget = false
	if targetCfg and targetCfg.enabled then
		if inEditMode and self._eqolPreview and previewIndex > 0 then
			if hoverCfg and hoverCfg.enabled then
				showTarget = previewIndex == 2
			else
				showTarget = previewIndex == 1
			end
		else
			showTarget = isTarget
		end
	end
	if showTarget then
		if targetFrame then
			local color = targetCfg.color or GFH.COLOR_WHITE
			targetFrame:SetBackdropBorderColor(color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1)
			targetFrame:Show()
		end
	else
		if targetFrame then targetFrame:Hide() end
	end

	local showAggro = false
	local aggroStatus
	if aggroCfg and aggroCfg.enabled then
		local aggroMode = GF.NormalizeAggroHighlightMode(aggroCfg.mode)
		local previewRole = st._previewRole or "DAMAGER"
		if inEditMode and self._eqolPreview and previewIndex > 0 then
			local sampleActive = aggroCfg.sample == true
			local roleAllowed = aggroMode ~= "NON_TANKS" or previewRole ~= "TANK"
			showAggro = sampleActive and roleAllowed
			aggroStatus = showAggro and 3 or nil
		else
			aggroStatus = GF.GetUnitThreatStatus(unit)
			showAggro = aggroStatus ~= nil
			if showAggro and aggroMode == "NON_TANKS" and getUnitRoleKey(unit) == "TANK" then showAggro = false end
		end
	end
	if showAggro then
		if aggroFrame then
			local r, g, b, a = GF.GetThreatHighlightColor(aggroStatus, aggroCfg and aggroCfg.color)
			aggroFrame:SetBackdropBorderColor(r or 1, g or 0, b or 0, a or 1)
			aggroFrame:Show()
		end
	else
		if aggroFrame then aggroFrame:Hide() end
	end

	local showHover = false
	if hoverCfg and hoverCfg.enabled then
		if inEditMode and self._eqolPreview and previewIndex > 0 then
			showHover = previewIndex == 1
		else
			showHover = st._hovered
		end
	end
	if showHover then
		if hoverFrame then
			local color = hoverCfg.color or GFH.COLOR_WHITE
			hoverFrame:SetBackdropBorderColor(color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1)
			hoverFrame:Show()
		end
	else
		if hoverFrame then hoverFrame:Hide() end
	end
end

local function getAuraCache(st, key)
	if not st then return nil end
	if key then
		st._auraCacheByKey = st._auraCacheByKey or {}
		local cache = st._auraCacheByKey[key]
		if not cache then
			cache = { auras = {}, order = {}, indexById = {} }
			st._auraCacheByKey[key] = cache
		end
		return cache
	end
	local cache = st._auraCache
	if not cache then
		cache = { auras = {}, order = {}, indexById = {} }
		st._auraCache = cache
	end
	return cache
end

local function resetAuraCache(cache)
	if not cache then return end
	local auras, order, indexById = cache.auras, cache.order, cache.indexById
	for k in pairs(auras) do
		auras[k] = nil
	end
	for i = #order, 1, -1 do
		order[i] = nil
	end
	for k in pairs(indexById) do
		indexById[k] = nil
	end
end

local AURA_KIND_HELPFUL = 1
local AURA_KIND_HARMFUL = 2
local AURA_KIND_EXTERNAL = 4
local AURA_KIND_DISPEL = 8

local function setAuraFlag(flags, flag)
	if not flags then return flag end
	if flags % (flag * 2) >= flag then return flags end
	return flags + flag
end

local function hasAuraFlag(flags, flag) return flags and flags % (flag * 2) >= flag end

local function clearDispelAuraState(st)
	if not st then return end
	st._dispelAuraId = nil
	st._dispelAuraIdDirty = nil
end

local function markDispelAuraDirty(st, auraId)
	if not st then return end
	if auraId == nil or st._dispelAuraId == auraId then
		st._dispelAuraId = nil
		st._dispelAuraIdDirty = true
	end
end

local function clearAuraKinds(st)
	if not st then return end
	st._auraKindById = st._auraKindById or {}
	local flagsById = st._auraKindById
	for k in pairs(flagsById) do
		flagsById[k] = nil
	end
	clearDispelAuraState(st)
end

local function getAuraKindFlags(unit, aura, helpfulFilter, harmfulFilter, externalFilter, dispelFilter, wantBuff, wantDebuff, wantExternals, wantsDispel, wantsHealerBuffPlacement, contextKind)
	if not (unit and aura and aura.auraInstanceID) then return nil end
	local globallyIgnored = UF.GlobalAuraIgnore and UF.GlobalAuraIgnore.ShouldIgnoreAura and UF.GlobalAuraIgnore.ShouldIgnoreAura(contextKind, aura)
	if globallyIgnored and not wantsHealerBuffPlacement then return nil end
	local flags
	local harmfulMatch, helpfulMatch

	if wantBuff or wantsHealerBuffPlacement then
		helpfulMatch = not globallyIgnored and UFHelper.IsAuraFilteredIn(unit, aura, helpfulFilter)
		if helpfulMatch then flags = setAuraFlag(flags, AURA_KIND_HELPFUL) end
		if wantsHealerBuffPlacement and UFHelper.IsHelpfulAura(aura, true) and aura.spellId and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.GetFamilyFromSpell then
			local healerTracked = UF.GroupFramesHealerBuffs.GetFamilyFromSpell(aura.spellId) ~= nil
			if healerTracked then flags = setAuraFlag(flags, 16) end
		end
	end

	if (wantDebuff or wantsDispel) and not globallyIgnored and not helpfulMatch then
		harmfulMatch = UFHelper.IsAuraFilteredIn(unit, aura, harmfulFilter)
		if wantDebuff and harmfulMatch then flags = setAuraFlag(flags, AURA_KIND_HARMFUL) end
	end

	if wantExternals and not globallyIgnored and not harmfulMatch and UFHelper.IsAuraFilteredIn(unit, aura, externalFilter) then flags = setAuraFlag(flags, AURA_KIND_EXTERNAL) end
	if wantsDispel and not globallyIgnored and UFHelper.IsAuraFilteredIn(unit, aura, dispelFilter) then flags = setAuraFlag(flags, AURA_KIND_DISPEL) end

	return flags
end

local function removeAuraFromGroupStore(cache, flagsById, auraId, keepExcludedMarker)
	if not (cache and auraId) then return end
	local hadAura = cache.auras and cache.auras[auraId] ~= nil
	local hadIndex = cache.indexById and cache.indexById[auraId] ~= nil
	cache.auras[auraId] = nil
	if cache.indexById then cache.indexById[auraId] = nil end
	if hadAura or hadIndex then cache._orderDirty = true end
	if flagsById then flagsById[auraId] = keepExcludedMarker and false or nil end
end

local function compactAuraOrder(cache)
	if not (cache and cache._orderDirty and cache.order and cache.indexById and cache.auras) then return end
	local order = cache.order
	local indexById = cache.indexById
	local auras = cache.auras
	for k in pairs(indexById) do
		indexById[k] = nil
	end
	local write = 1
	for read = 1, #order do
		local auraId = order[read]
		if auraId and auras[auraId] and not indexById[auraId] then
			order[write] = auraId
			indexById[auraId] = write
			write = write + 1
		end
	end
	for i = write, #order do
		order[i] = nil
	end
	cache._orderDirty = nil
end

local function cacheAuraWithFlags(st, flagsById, aura, flags)
	if not (st and aura and aura.auraInstanceID) then return end
	local auraId = aura.auraInstanceID
	local prevFlags = flagsById and flagsById[auraId]
	if flags then
		local buffCache = getAuraCache(st, "buff")
		local debuffCache = getAuraCache(st, "debuff")
		if hasAuraFlag(flags, AURA_KIND_HELPFUL) or hasAuraFlag(flags, AURA_KIND_EXTERNAL) or hasAuraFlag(flags, 16) then
			buffCache.auras[auraId] = aura
			if AuraUtil and AuraUtil.addAuraToOrder then
				AuraUtil.addAuraToOrder(buffCache, auraId)
			elseif not buffCache.indexById[auraId] then
				buffCache.order[#buffCache.order + 1] = auraId
				buffCache.indexById[auraId] = #buffCache.order
			end
		else
			removeAuraFromGroupStore(buffCache, nil, auraId)
		end
		if hasAuraFlag(flags, AURA_KIND_HARMFUL) or hasAuraFlag(flags, AURA_KIND_DISPEL) then
			debuffCache.auras[auraId] = aura
			if AuraUtil and AuraUtil.addAuraToOrder then
				AuraUtil.addAuraToOrder(debuffCache, auraId)
			elseif not debuffCache.indexById[auraId] then
				debuffCache.order[#debuffCache.order + 1] = auraId
				debuffCache.indexById[auraId] = #debuffCache.order
			end
		else
			removeAuraFromGroupStore(debuffCache, nil, auraId)
		end
		if flagsById then flagsById[auraId] = flags end
		local hadDispel = hasAuraFlag(prevFlags, AURA_KIND_DISPEL)
		local hasDispel = hasAuraFlag(flags, AURA_KIND_DISPEL)
		if st._dispelAuraId == auraId and not hasDispel then
			st._dispelAuraId = nil
			st._dispelAuraIdDirty = true
		elseif (not st._dispelAuraId) and hasDispel then
			st._dispelAuraId = auraId
			st._dispelAuraIdDirty = nil
		elseif hadDispel and not hasDispel and not st._dispelAuraId then
			st._dispelAuraIdDirty = true
		end
	else
		markDispelAuraDirty(st, auraId)
		removeAuraFromGroupStore(getAuraCache(st, "buff"), nil, auraId)
		removeAuraFromGroupStore(getAuraCache(st, "debuff"), nil, auraId)
		if flagsById then flagsById[auraId] = false end
	end
end

local AURA_TYPE_META = {
	buff = {
		containerKey = "buffContainer",
		buttonsKey = "buffButtons",
		filter = "HELPFUL",
		isDebuff = false,
	},
	debuff = {
		containerKey = "debuffContainer",
		buttonsKey = "debuffButtons",
		filter = "HARMFUL",
		isDebuff = true,
	},
	externals = {
		containerKey = "externalContainer",
		buttonsKey = "externalButtons",
		filter = "HELPFUL",
		isDebuff = false,
	},
}

function GF.ResolveAuraTooltipEnabled(groupKind, kindKey, typeCfg)
	if typeCfg and typeCfg.showTooltip ~= nil then return typeCfg.showTooltip == true end
	local def = DEFAULTS[groupKind] and DEFAULTS[groupKind].auras and DEFAULTS[groupKind].auras[kindKey]
	if def and def.showTooltip ~= nil then return def.showTooltip == true end
	return false
end

local SAMPLE_BUFF_ICONS = { 136243, 135940, 136085, 136097, 136116, 136048, 135932, 136108 }
local SAMPLE_DEBUFF_ICONS = { 136207, 136160, 136128, 135804, 136168, 132104, 136118, 136214 }
local SAMPLE_EXTERNAL_ICONS = { 135936, 136073, 135907, 135940, 136090, 135978 }
local SAMPLE_DISPEL_TYPES = { "Magic", "Curse", "Disease", "Poison" }

local function getSampleAuraData(kindKey, index, now)
	local duration
	if index % 3 == 0 then
		duration = 120
	elseif index % 3 == 1 then
		duration = 30
	else
		duration = 0
	end
	local expiration = duration > 0 and (now + duration) or nil
	local stacks
	if index % 5 == 0 then
		stacks = 5
	elseif index % 3 == 0 then
		stacks = 3
	end
	local iconList = SAMPLE_BUFF_ICONS
	if kindKey == "debuff" then
		iconList = SAMPLE_DEBUFF_ICONS
	elseif kindKey == "externals" then
		iconList = SAMPLE_EXTERNAL_ICONS
	end
	local icon = iconList[((index - 1) % #iconList) + 1]
	local dispelName = kindKey == "debuff" and SAMPLE_DISPEL_TYPES[((index - 1) % #SAMPLE_DISPEL_TYPES) + 1] or nil
	local canActivePlayerDispel = dispelName == "Magic"
	local base = (kindKey == "buff" and -100000) or (kindKey == "debuff" and -200000) or -300000
	local auraId = base - index
	local points
	if kindKey == "externals" then points = { 20 + ((index - 1) % 3) * 10 } end
	return {
		auraInstanceID = auraId,
		icon = icon,
		isHelpful = kindKey ~= "debuff",
		isHarmful = kindKey == "debuff",
		applications = stacks,
		duration = duration,
		expirationTime = expiration,
		dispelName = dispelName,
		canActivePlayerDispel = canActivePlayerDispel,
		points = points,
		isSample = true,
	}
end

local function getSampleStyle(st, kindKey, style)
	st._auraSampleStyle = st._auraSampleStyle or {}
	local sample = st._auraSampleStyle[kindKey]
	if not sample or sample._src ~= style then
		sample = {}
		sample._src = style
		st._auraSampleStyle[kindKey] = sample
	else
		for key in pairs(sample) do
			if key ~= "_src" and key ~= "showTooltip" then sample[key] = nil end
		end
	end
	for key, value in pairs(style or {}) do
		sample[key] = value
	end
	sample.showTooltip = false
	st._auraSampleStyle[kindKey] = sample
	return sample
end

function GF:LayoutAuras(self)
	local st = getState(self)
	if not st then return end
	local groupKind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(groupKind)
	local ac = cfg and cfg.auras
	if not ac then return end
	local contentScale = GF.GetDynamicContentScale(self, cfg)
	GFH.SyncAurasEnabled(cfg)
	local wantsAuras = (ac.buff and ac.buff.enabled) or (ac.debuff and ac.debuff.enabled) or (ac.externals and ac.externals.enabled)
	if not wantsAuras then return end

	st._auraLayout = st._auraLayout or {}
	st._auraLayoutKey = st._auraLayoutKey or {}
	st._auraStyle = st._auraStyle or {}

	local parent = GF.GetLayoutAnchorFrame(st, st.barGroup or st.frame)

	for kindKey, meta in pairs(AURA_TYPE_META) do
		local typeCfg = ac[kindKey] or {}
		if typeCfg.enabled == false then
			local container = st[meta.containerKey]
			if container then container:Hide() end
			hideAuraButtons(st[meta.buttonsKey], 1)
			st._auraLayout[kindKey] = nil
			st._auraLayoutKey[kindKey] = nil
		else
			local anchorPoint, primary, secondary = resolveAuraGrowth(typeCfg.anchorPoint, typeCfg.growth, typeCfg.growthX, typeCfg.growthY)
			local anchorOutside = typeCfg.anchorOutside == true
			local containerPoint = GF.ResolveAuraContainerBoundaryPoint(anchorPoint, anchorOutside, primary, secondary, self)
			local size = (tonumber(typeCfg.size) or 16) * contentScale
			local spacing = (tonumber(typeCfg.spacing) or 2) * contentScale
			local perRow = tonumber(typeCfg.perRow) or tonumber(typeCfg.max) or 6
			if perRow < 1 then perRow = 1 end
			local maxCount = GF.ClampAuraCount(typeCfg.max, perRow) or perRow
			local x = (tonumber(typeCfg.x) or 0) * contentScale
			local y = (tonumber(typeCfg.y) or 0) * contentScale
			local scale = GFH.GetEffectiveScale(parent)
			size = roundToPixel(size, scale)
			spacing = roundToPixel(spacing, scale)
			x = roundToPixel(x, scale)
			y = roundToPixel(y, scale)

			local key = anchorPoint
				.. "|"
				.. tostring(anchorOutside)
				.. "|"
				.. tostring(primary)
				.. "|"
				.. tostring(secondary)
				.. "|"
				.. size
				.. "|"
				.. spacing
				.. "|"
				.. perRow
				.. "|"
				.. maxCount
				.. "|"
				.. x
				.. "|"
				.. y
			local layout = st._auraLayout[kindKey] or {}
			layout.anchorPoint = anchorPoint
			layout.containerPoint = containerPoint
			layout.anchorOutside = anchorOutside
			layout.primary = primary
			layout.secondary = secondary
			layout.size = size
			layout.spacing = spacing
			layout.perRow = perRow
			layout.maxCount = maxCount
			layout.x = x
			layout.y = y
			layout.key = key
			st._auraLayout[kindKey] = layout

			if st._auraLayoutKey[kindKey] ~= key then
				st._auraLayoutKey[kindKey] = key
				local container = ensureAuraContainer(st, meta.containerKey)
				if container then
					container:ClearAllPoints()
					if Pixel and Pixel.SetPoint then
						Pixel.SetPoint(container, containerPoint, parent, anchorPoint, x, y)
					else
						container:SetPoint(containerPoint, parent, anchorPoint, x, y)
					end
					updateAuraContainerSize(container, anchorPoint, maxCount, maxCount, perRow, size, spacing, primary)
					if container.SetClipsChildren then container:SetClipsChildren(false) end
				end
				local buttons = st[meta.buttonsKey]
				if buttons and container then
					for i, btn in ipairs(buttons) do
						if AuraUtil and AuraUtil.setAuraButtonSize then
							AuraUtil.setAuraButtonSize(btn, size)
						elseif btn.SetSize then
							if Pixel and Pixel.SetSize then
								Pixel.SetSize(btn, size, size, 1, 1)
							else
								btn:SetSize(size, size)
							end
							btn._eqolAuraButtonSize = size
						end
						positionAuraButton(btn, container, primary, secondary, i, perRow, size, spacing)
						btn._auraLayoutKey = key
					end
				end
			end

			local style = st._auraStyle[kindKey] or {}
			style.size = size
			style.padding = spacing
			style.showTooltip = GF.ResolveAuraTooltipEnabled(groupKind, kindKey, typeCfg)
			style.tooltipUseEditMode = st._tooltipUseEditMode == true
			style.tooltipAnchor = "ANCHOR_RIGHT"
			style.showCooldown = typeCfg.showCooldown ~= false
			style.blizzardDispelBorder = typeCfg.showDispelIcon == true
			style.borderColor = typeCfg.borderColor
			style.iconShape = GF.NormalizeAuraIconShape(typeCfg.iconShape, "DEFAULT")
			style.borderTexture = GF.NormalizeAuraBorderTexture(typeCfg.borderTexture, "DEFAULT", style.iconShape)
			style.borderSize = GF.ScaleContentValue(self, typeCfg.borderSize, cfg, 1)
			style.borderOffset = GF.ScaleContentValue(self, typeCfg.borderOffset, cfg, 0)
			if typeCfg.showCooldownText ~= nil then style.showCooldownText = typeCfg.showCooldownText end
			style.cooldownAnchor = typeCfg.cooldownAnchor
			style.cooldownOffset = GF.ScaleOffset(typeCfg.cooldownOffset, contentScale)
			style.cooldownFont = typeCfg.cooldownFont
			style.countFont = typeCfg.countFont
			style.countFontSize = GF.ScaleContentValue(self, typeCfg.countFontSize, cfg, 1)
			style.countFontOutline = typeCfg.countFontOutline
			style.cooldownFontSize = GF.ScaleContentValue(self, typeCfg.cooldownFontSize, cfg, 1)
			style.cooldownFontOutline = typeCfg.cooldownFontOutline
			if typeCfg.showStacks ~= nil then style.showStacks = typeCfg.showStacks end
			style.countAnchor = typeCfg.countAnchor
			style.countOffset = GF.ScaleOffset(typeCfg.countOffset, contentScale)
			style.showDR = typeCfg.showDR == true
			style.drAnchor = typeCfg.drAnchor
			style.drOffset = GF.ScaleOffset(typeCfg.drOffset, contentScale)
			style.drFont = typeCfg.drFont
			style.drFontSize = GF.ScaleContentValue(self, typeCfg.drFontSize, cfg, 1)
			style.drFontOutline = typeCfg.drFontOutline
			style.drColor = typeCfg.drColor
			if kindKey == "externals" then
				local def = (DEFAULTS[groupKind] and DEFAULTS[groupKind].auras and DEFAULTS[groupKind].auras.externals) or EMPTY
				local glowEnabled = typeCfg.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				style.externalGlowEnabled = glowEnabled == true
				style.externalGlowStyle = GF.NormalizeExternalGlowStyleForIconShape(typeCfg.glowStyle, def.glowStyle or "MARCHING_ANTS", style.iconShape)
				style.externalGlowInset = GF.ScaleContentValue(self, GF.NormalizeExternalGlowInset(typeCfg.glowInset, def.glowInset or 0), cfg, 0)
				local r, g, b, a = unpackColor(GF.GetExternalGlowColor(typeCfg, def), { 1, 0.25, 0.25, 1 })
				local glowColor = style.externalGlowColor or {}
				glowColor[1], glowColor[2], glowColor[3], glowColor[4] = r, g, b, a
				style.externalGlowColor = glowColor
			end
			st._auraStyle[kindKey] = style
		end
	end
end

local function updateAuraType(self, unit, st, ac, kindKey, cache, changed, healerBuffCompiled)
	local meta = AURA_TYPE_META[kindKey]
	if not meta then return end
	local typeCfg = (ac and ac[kindKey]) or EMPTY
	if typeCfg.enabled == false then
		local container = st[meta.containerKey]
		if container then container:Hide() end
		hideAuraButtons(st[meta.buttonsKey], 1)
		return
	end

	local layout = st._auraLayout and st._auraLayout[kindKey]
	local style = st._auraStyle and st._auraStyle[kindKey]
	if not (layout and style) then return end

	local container = ensureAuraContainer(st, meta.containerKey)
	if not container then return end
	container:Show()

	local buttons = st[meta.buttonsKey]
	if not buttons then
		buttons = {}
		st[meta.buttonsKey] = buttons
	end
	if not cache then
		hideAuraButtons(buttons, 1)
		return
	end
	local auras = cache.auras
	local order = cache.order
	if not (auras and order) then
		hideAuraButtons(buttons, 1)
		return
	end
	local flags = st._auraKindById
	local externalsEnabled = ac and ac.externals and ac.externals.enabled ~= false
	local suppressHealerBuffAura = nil
	local suppressKind = self and (self._eqolGroupKind or "party")
	local suppressCfg = self and self._eqolCfg
	if kindKey == "buff" and st._wantsHealerBuffPlacement == true and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ShouldSuppressRegularBuffAura then
		suppressHealerBuffAura = UF.GroupFramesHealerBuffs.ShouldSuppressRegularBuffAura
	end
	local shown = 0
	local maxCount = layout.maxCount or 0
	style.tooltipUseEditMode = st._tooltipUseEditMode == true
	style.tooltipAnchor = "ANCHOR_RIGHT"
	for i = 1, #order do
		if shown >= maxCount then break end
		local auraId = order[i]
		local aura = auraId and auras[auraId]
		if aura then
			local auraFlags = flags and flags[auraId]
			local match = false
			if kindKey == "debuff" then
				match = hasAuraFlag(auraFlags, AURA_KIND_HARMFUL)
			elseif kindKey == "buff" then
				match = hasAuraFlag(auraFlags, AURA_KIND_HELPFUL)
				if match and externalsEnabled and hasAuraFlag(auraFlags, AURA_KIND_EXTERNAL) then match = false end
				if match and suppressHealerBuffAura and suppressHealerBuffAura(suppressKind, suppressCfg, aura, healerBuffCompiled, unit) then match = false end
			elseif kindKey == "externals" then
				match = hasAuraFlag(auraFlags, AURA_KIND_EXTERNAL)
			end
			if match then
				shown = shown + 1
				local btn = buttons and buttons[shown]
				if not btn then btn = AuraUtil.ensureAuraButton(container, buttons, shown, style) end
				local auraIdForBtn = aura.auraInstanceID or auraId
				if btn.unitToken ~= unit or btn.auraInstanceID ~= auraIdForBtn or (changed and auraIdForBtn and changed[auraIdForBtn]) then
					AuraUtil.applyAuraToButton(btn, aura, style, meta.isDebuff, unit)
				end
				if kindKey == "externals" then GF.ApplyExternalGlow(btn, style) end
				setAuraTooltipState(btn, style)
				if btn._auraLayoutKey ~= layout.key then
					positionAuraButton(btn, container, layout.primary, layout.secondary, shown, layout.perRow, layout.size, layout.spacing)
					btn._auraLayoutKey = layout.key
				end
				btn:Show()
			end
		end
	end
	updateAuraContainerSize(container, layout.anchorPoint, shown, layout.maxCount or shown, layout.perRow, layout.size, layout.spacing, layout.primary)
	hideAuraButtons(buttons, shown + 1)
end

function GF.GetGroupAuraRenderer(cfg, def)
	local ac = cfg and cfg.auras
	local defAc = def and def.auras
	local renderer = ac and ac.renderer
	if renderer == nil and defAc then renderer = defAc.renderer end
	if UFHelper and UFHelper.NormalizeAuraRenderer then return UFHelper.NormalizeAuraRenderer(renderer) end
	local token = tostring(renderer or "CUSTOM"):upper()
	return (token == "BLIZZARD" or token == "BLIZZARD_CONTAINER") and "BLIZZARD" or "CUSTOM"
end

function GF.IsGroupBlizzardAuraRenderer(cfg, def) return GF.GetGroupAuraRenderer(cfg, def) == "BLIZZARD" end

GF.BLIZZARD_AURA_RENDER_TYPES = {
	buffs = true,
	debuffs = true,
	dispels = true,
	externals = true,
	privateAuras = true,
}

GF.BLIZZARD_AURA_SETTING_FIELDS = {
	buffs = {
		buffsEnabled = true,
		buffMax = true,
	},
	debuffs = {
		debuffsEnabled = true,
		debuffDisplayLargerRoleSpecific = true,
		debuffMax = true,
	},
	externals = {
		externalsEnabled = true,
	},
	privateAuras = {
		privateAurasAmount = true,
		privateAurasEnabled = true,
	},
}

function GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, key)
	if not GF.IsGroupBlizzardAuraRenderer(cfg, def) then return false end
	local ac = cfg and cfg.auras
	local defAc = def and def.auras
	local selection = ac and ac.blizzardTypes
	if type(selection) ~= "table" then selection = defAc and defAc.blizzardTypes end
	if type(selection) ~= "table" then return GF.BLIZZARD_AURA_RENDER_TYPES[key] == true end
	return selection[key] == true
end

function GF.IsHealerBuffPlacementSuppressedByBlizzard(kind, cfg)
	local def = DEFAULTS[kind or "party"] or EMPTY
	cfg = cfg or getCfg(kind or "party")
	return GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "buffs")
end

function GF.IsBlizzardAuraCooldownTextEnabled(cfg, def)
	local ac = cfg and cfg.auras
	if ac and ac.blizzardShowCooldownText ~= nil then return ac.blizzardShowCooldownText == true end
	local defAc = def and def.auras
	if defAc and defAc.blizzardShowCooldownText ~= nil then return defAc.blizzardShowCooldownText == true end
	return true
end

function GF.NormalizeBlizzardDispelIndicatorMode(value)
	if value == nil then return nil end
	local token = tostring(value):upper()
	if token == GF.BLIZZARD_DISPEL_MODE_BY_ME or token == "BYME" or token == "ME" or token == "DISPELLABLE_BY_ME" or token == "DISPELLABLEBYME" or token == "1" then
		return GF.BLIZZARD_DISPEL_MODE_BY_ME
	end
	if token == GF.BLIZZARD_DISPEL_MODE_ALL or token == "DISPLAY_ALL" or token == "DISPLAYALL" or token == "2" then return GF.BLIZZARD_DISPEL_MODE_ALL end
	return nil
end

function GF.GetBlizzardDispelIndicatorMode(cfg, def)
	local ac = cfg and cfg.auras
	local mode = GF.NormalizeBlizzardDispelIndicatorMode(ac and ac.blizzardDispelIndicatorMode)
	if mode then return mode end
	local defAc = def and def.auras
	mode = GF.NormalizeBlizzardDispelIndicatorMode(defAc and defAc.blizzardDispelIndicatorMode)
	return mode or GF.BLIZZARD_DISPEL_MODE_ALL
end

function GF.GetBlizzardDispelIndicatorOption(cfg, def)
	local mode = GF.GetBlizzardDispelIndicatorMode(cfg, def)
	if mode == GF.BLIZZARD_DISPEL_MODE_BY_ME then
		return (Enum and Enum.RaidDispelDisplayType and Enum.RaidDispelDisplayType.DispellableByMe) or 1
	end
	return (Enum and Enum.RaidDispelDisplayType and Enum.RaidDispelDisplayType.DisplayAll) or 2
end

function GF.GetDispelTintBlizzardDispelIndicatorMode(cfg, def)
	local sc = cfg and cfg.status
	local dcfg = sc and sc.dispelTint
	local mode = GF.NormalizeBlizzardDispelIndicatorMode(dcfg and dcfg.blizzardDispelIndicatorMode)
	if mode then return mode end
	local defSc = def and def.status
	local defDispel = defSc and defSc.dispelTint
	mode = GF.NormalizeBlizzardDispelIndicatorMode(defDispel and defDispel.blizzardDispelIndicatorMode)
	return mode or GF.GetBlizzardDispelIndicatorMode(cfg, def)
end

function GF.GetDispelTintBlizzardDispelIndicatorOption(cfg, def)
	local mode = GF.GetDispelTintBlizzardDispelIndicatorMode(cfg, def)
	if mode == GF.BLIZZARD_DISPEL_MODE_BY_ME then
		return (Enum and Enum.RaidDispelDisplayType and Enum.RaidDispelDisplayType.DispellableByMe) or 1
	end
	return (Enum and Enum.RaidDispelDisplayType and Enum.RaidDispelDisplayType.DisplayAll) or 2
end

function GF.ResolveBlizzardPrivateAuraDispelsEnabled(cfg, def)
	local sc = cfg and cfg.status
	local dcfg = sc and sc.dispelTint
	local enabled = dcfg and dcfg.blizzardPrivateAuraDispels
	if enabled ~= nil then return enabled == true end
	local defSc = def and def.status
	local defDispel = defSc and defSc.dispelTint
	return defDispel and defDispel.blizzardPrivateAuraDispels == true
end

function GF.NormalizeBlizzardAuraOrganization(value)
	if value == nil then return nil end
	if type(value) == "number" then
		if Enum and Enum.RaidAuraOrganizationType then
			if value == Enum.RaidAuraOrganizationType.BuffsTopDebuffsBottom then return GF.BLIZZARD_AURA_ORGANIZATION_TOP_BOTTOM end
			if value == Enum.RaidAuraOrganizationType.BuffsRightDebuffsLeft then return GF.BLIZZARD_AURA_ORGANIZATION_RIGHT_LEFT end
			if value == Enum.RaidAuraOrganizationType.Legacy then return GF.BLIZZARD_AURA_ORGANIZATION_LEGACY end
		end
		if value == 1 then return GF.BLIZZARD_AURA_ORGANIZATION_TOP_BOTTOM end
		if value == 2 then return GF.BLIZZARD_AURA_ORGANIZATION_RIGHT_LEFT end
		return GF.BLIZZARD_AURA_ORGANIZATION_LEGACY
	end
	local token = tostring(value):upper()
	if token == GF.BLIZZARD_AURA_ORGANIZATION_TOP_BOTTOM or token == "TOP_BOTTOM" or token == "BUFFS_TOP" or token == "1" then return GF.BLIZZARD_AURA_ORGANIZATION_TOP_BOTTOM end
	if token == GF.BLIZZARD_AURA_ORGANIZATION_RIGHT_LEFT or token == "RIGHT_LEFT" or token == "BUFFS_RIGHT" or token == "2" then return GF.BLIZZARD_AURA_ORGANIZATION_RIGHT_LEFT end
	if token == GF.BLIZZARD_AURA_ORGANIZATION_LEGACY or token == "0" then return GF.BLIZZARD_AURA_ORGANIZATION_LEGACY end
	return nil
end

function GF.GetBlizzardAuraOrganization(cfg, def)
	local ac = cfg and cfg.auras
	local organization = GF.NormalizeBlizzardAuraOrganization(ac and ac.blizzardOrganizationType)
	if organization then return organization end
	local defAc = def and def.auras
	organization = GF.NormalizeBlizzardAuraOrganization(defAc and defAc.blizzardOrganizationType)
	return organization or GF.BLIZZARD_AURA_ORGANIZATION_LEGACY
end

function GF.IsBlizzardLargerRoleDebuffEnabled(cfg, def)
	local debuff = cfg and cfg.auras and cfg.auras.debuff
	if debuff and debuff.displayLargerRoleSpecificDebuffs ~= nil then return debuff.displayLargerRoleSpecificDebuffs == true end
	local defDebuff = def and def.auras and def.auras.debuff
	if defDebuff and defDebuff.displayLargerRoleSpecificDebuffs ~= nil then return defDebuff.displayLargerRoleSpecificDebuffs == true end
	return true
end

function GF.GetBlizzardAuraIconSize(cfg, def)
	local ac = cfg and cfg.auras
	local defAuras = def and def.auras
	local configured = ac and tonumber(ac.blizzardIconSize)
	if not configured and defAuras then configured = tonumber(defAuras.blizzardIconSize) end
	if configured then return clampNumber(configured, 8, 120, configured) end

	local buff = (ac and ac.buff) or (defAuras and defAuras.buff) or EMPTY
	local debuff = (ac and ac.debuff) or (defAuras and defAuras.debuff) or EMPTY
	local externals = (ac and ac.externals) or (defAuras and defAuras.externals) or EMPTY
	local privateAuras = (cfg and cfg.privateAuras) or (def and def.privateAuras) or EMPTY
	local privateIcon = privateAuras.icon or EMPTY
	return clampNumber(buff.size or debuff.size or externals.size or privateIcon.size or 16, 8, 120, 16)
end

function GF.EnsureBlizzardAuraRenderTypes(cfg)
	if not cfg then return nil end
	cfg.auras = cfg.auras or {}
	local selection = cfg.auras.blizzardTypes
	if type(selection) ~= "table" then
		selection = GF._sharedEdit.csm(GF.BLIZZARD_AURA_RENDER_TYPES)
		cfg.auras.blizzardTypes = selection
	end
	return selection
end

function GF.IsBlizzardAuraRenderTypeActive(kind, sectionId)
	local cfg = getCfg(kind)
	local def = DEFAULTS[kind] or EMPTY
	if not GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, sectionId) then return false end
	if sectionId == "dispels" then return true end
	if sectionId == "privateAuras" then
		local pcfg = (cfg and cfg.privateAuras) or def.privateAuras or EMPTY
		return pcfg.enabled == true
	end
	local ac = (cfg and cfg.auras) or EMPTY
	local defAuras = def.auras or EMPTY
	local typeCfg = (sectionId == "buffs" and ac.buff) or (sectionId == "debuffs" and ac.debuff) or (sectionId == "externals" and ac.externals) or EMPTY
	local defType = (sectionId == "buffs" and defAuras.buff) or (sectionId == "debuffs" and defAuras.debuff) or (sectionId == "externals" and defAuras.externals) or EMPTY
	if typeCfg.enabled ~= nil then return typeCfg.enabled == true end
	return defType.enabled == true
end

function GF.IsBlizzardAuraSettingFieldVisible(kind, sectionId, field)
	if not sectionId then return true end
	local cfg = getCfg(kind)
	local def = DEFAULTS[kind] or EMPTY
	if not GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, sectionId) then return true end
	if not field then return false end
	local allowed = GF.BLIZZARD_AURA_SETTING_FIELDS[sectionId]
	if allowed and allowed[field] then return true end
	return false
end

function GF.CombineSettingShown(setting, predicate)
	if not (setting and predicate) then return end
	local original = setting.isShown
	setting.isShown = function(...)
		if original then
			local ok, shown = pcall(original, ...)
			if ok and shown == false then return false end
		end
		return predicate(setting) ~= false
	end
end

function GF.ApplyBlizzardAuraSettingVisibility(settings, kind)
	for _, setting in ipairs(settings or EMPTY) do
		if setting then
			local sectionId = setting.parentId
			if sectionId == "buffs" or sectionId == "debuffs" or sectionId == "externals" or sectionId == "privateAuras" then
				GF.CombineSettingShown(setting, function(entry) return GF.IsBlizzardAuraSettingFieldVisible(kind, sectionId, entry.field) end)
			elseif setting.id == "dispeltint" or setting.parentId == "dispeltint" then
				GF.CombineSettingShown(setting, function()
					local cfg = getCfg(kind)
					return not GF.IsBlizzardAuraRenderTypeEnabled(cfg, DEFAULTS[kind] or EMPTY, "dispels")
				end)
			end
		end
	end
end

function GF:ClearBlizzardAuraContainer(self)
	local st = getState(self)
	local container = st and st.blizzardAuras
	if not container then return end
	if UFHelper and UFHelper.RemovePrivateAuras then
		local removed = UFHelper.RemovePrivateAuras(container)
		if removed == false then GF.MarkEditModeReloadRequired() end
	end
	if container.Hide then container:Hide() end
end

function GF:ClearPrivateAuraDispelContainer(self)
	local st = getState(self)
	local container = st and st.privateAuraDispels
	if not container then return end
	if UFHelper and UFHelper.ClearDeferredPrivateAuraMutation then UFHelper.ClearDeferredPrivateAuraMutation(container) end
	if UFHelper and UFHelper.RemoveBlizzardAuraContainer then
		local removed = UFHelper.RemoveBlizzardAuraContainer(container)
		if removed == false then GF.MarkEditModeReloadRequired() end
	elseif UFHelper and UFHelper.RemovePrivateAuras then
		local removed = UFHelper.RemovePrivateAuras(container)
		if removed == false then GF.MarkEditModeReloadRequired() end
	end
	if container.Hide then container:Hide() end
end

function GF:UpdatePrivateAuraDispelContainerVisibility(self)
	local st = getState(self)
	local container = st and st.privateAuraDispels
	if not (container and container.SetAlpha) then return end
	if st._dispelTintShown == true or st._dispelGlowActive == true then
		container:SetAlpha(0)
		return
	end
	if C_Timer and C_Timer.After then
		C_Timer.After(0, function()
			local current = getState(self)
			local currentContainer = current and current.privateAuraDispels
			if not (currentContainer and currentContainer.SetAlpha) then return end
			if current._dispelTintShown == true or current._dispelGlowActive == true then return end
			currentContainer:SetAlpha(1)
		end)
	else
		container:SetAlpha(1)
	end
end

function GF:UpdatePrivateAuraDispelContainer(self)
	if not (self and UFHelper and UFHelper.ApplyBlizzardAuraContainer) then return end
	local st = getState(self)
	if not st then return end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local def = DEFAULTS[kind] or EMPTY
	if GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "dispels") or not GF.ResolveBlizzardPrivateAuraDispelsEnabled(cfg, def) then
		GF:ClearPrivateAuraDispelContainer(self)
		return
	end
	local unit = getUnit(self)
	if not unit then
		GF:ClearPrivateAuraDispelContainer(self)
		return
	end
	local parent = st.barGroup or st.health or GF.GetLayoutAnchorFrame(st, self) or self
	local levelParent = st.statusIconLayer or st.healthTextLayer or st.dispelTint or parent
	if not st.privateAuraDispels then
		st.privateAuraDispels = CreateFrame("Frame", nil, parent)
		st.privateAuraDispels:EnableMouse(false)
	elseif st.privateAuraDispels.GetParent and parent and st.privateAuraDispels:GetParent() ~= parent then
		st.privateAuraDispels:SetParent(parent)
	end
	UFHelper.ApplyBlizzardAuraContainer(st.privateAuraDispels, unit, {
		showBuffs = false,
		showDebuffs = false,
		showDispels = true,
		showDispelOverlay = true,
		showBigDefensive = false,
		maxBuffs = 0,
		maxDebuffs = 0,
		maxDispelDebuffs = 1,
		iconSize = 10,
		organizationType = GF.GetBlizzardAuraOrganization(cfg, def),
		dispelIndicatorOption = GF.GetDispelTintBlizzardDispelIndicatorOption(cfg, def),
		powerBarUsedHeight = 0,
		groupType = (kind == "party") and 4 or 5,
		displayLargerRoleSpecificDebuffs = false,
		showCountdownFrame = false,
		showCountdownNumbers = false,
		alwaysHideDuration = true,
		suppressDispelBorderIcons = true,
		privateAuraFrameLevelWorkaround = false,
	}, parent, levelParent, false)
	GF:UpdatePrivateAuraDispelContainerVisibility(self)
end

function GF:UpdateBlizzardAuraContainer(self)
	if not (self and UFHelper and UFHelper.ApplyBlizzardAuraContainer) then return end
	local st = getState(self)
	local unit = getUnit(self)
	if not st then return end
	if not unit then
		GF:ClearBlizzardAuraContainer(self)
		return
	end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local def = DEFAULTS[kind] or EMPTY
	if not GF.IsGroupBlizzardAuraRenderer(cfg, def) then
		GF:ClearBlizzardAuraContainer(self)
		return
	end
	local inEditMode = isEditModeActive()
	local showSample = inEditMode == true and GF._editModeSampleAuras ~= false and GF._editModeExiting ~= true
	if inEditMode and not showSample then
		GF:ClearBlizzardAuraContainer(self)
		return
	end
	if cfg then GFH.SyncAurasEnabled(cfg) end
	local ac = (cfg and cfg.auras) or EMPTY
	local defAuras = def.auras or EMPTY
	local buff = ac.buff or defAuras.buff or EMPTY
	local debuff = ac.debuff or defAuras.debuff or EMPTY
	local externals = ac.externals or defAuras.externals or EMPTY
	local defBuff = defAuras.buff or EMPTY
	local defDebuff = defAuras.debuff or EMPTY
	local defExternals = defAuras.externals or EMPTY
	local pcfg = (cfg and cfg.privateAuras) or def.privateAuras or EMPTY
	local privateIcon = pcfg.icon or EMPTY
	local showBuffs = GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "buffs") and ((buff.enabled ~= nil) and (buff.enabled == true) or (defBuff.enabled == true))
	local renderDebuffs = GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "debuffs") and ((debuff.enabled ~= nil) and (debuff.enabled == true) or (defDebuff.enabled == true))
	local privateEnabled = GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "privateAuras") and pcfg.enabled == true
	local showDebuffs = renderDebuffs or privateEnabled
	local showBigDefensive = GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "externals") and ((externals.enabled ~= nil) and (externals.enabled == true) or (defExternals.enabled == true))
	local showDispels = GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "dispels")
	local iconSize = GF.GetBlizzardAuraIconSize(cfg, def)
	local maxDebuffs = renderDebuffs and (debuff.max or defDebuff.max or privateIcon.amount or 0) or 0
	if privateEnabled and maxDebuffs < (privateIcon.amount or 0) then maxDebuffs = privateIcon.amount or maxDebuffs end
	if not (showBuffs or showDebuffs or showDispels or showBigDefensive) then
		GF:ClearBlizzardAuraContainer(self)
		return
	end
	local privateAuraParent = st.health or GF.GetLayoutAnchorFrame(st, self) or self
	local privateAuraLevelParent = st.statusIconLayer or st.healthTextLayer or privateAuraParent or st.health or st.barGroup or self
	if not st.blizzardAuras then
		st.blizzardAuras = CreateFrame("Frame", nil, privateAuraParent)
		st.blizzardAuras:EnableMouse(false)
	elseif st.blizzardAuras.GetParent and privateAuraParent and st.blizzardAuras:GetParent() ~= privateAuraParent then
		st.blizzardAuras:SetParent(privateAuraParent)
	end
	UFHelper.ApplyBlizzardAuraContainer(st.blizzardAuras, unit, {
		showBuffs = showBuffs,
		showDebuffs = showDebuffs,
		showDispels = showDispels,
		showDispelOverlay = showDispels,
		showBigDefensive = showBigDefensive,
		maxBuffs = showBuffs and (GF.ClampAuraCount(buff.max or defBuff.max, 6, 20) or 6) or 0,
		maxDebuffs = maxDebuffs,
		maxDispelDebuffs = 3,
		iconSize = iconSize,
		bigDefensiveSize = (showBigDefensive and (externals.size or defExternals.size or iconSize)) or iconSize,
		organizationType = GF.GetBlizzardAuraOrganization(cfg, def),
		dispelIndicatorOption = GF.GetBlizzardDispelIndicatorOption(cfg, def),
		powerBarUsedHeight = 0,
		groupType = (kind == "party") and 4 or 5,
		displayLargerRoleSpecificDebuffs = GF.IsBlizzardLargerRoleDebuffEnabled(cfg, def),
		showCountdownFrame = true,
		showCountdownNumbers = GF.IsBlizzardAuraCooldownTextEnabled(cfg, def),
	}, privateAuraParent, privateAuraLevelParent, showSample)
end

local function fullScanGroupAuras(
	unit,
	st,
	helpfulFilter,
	harmfulFilter,
	harmfulScanFilter,
	externalFilter,
	dispelFilter,
	wantBuff,
	wantDebuff,
	wantExternals,
	wantsDispel,
	wantsHealerBuffPlacement,
	healerBuffNeedsWideScan,
	queryMax,
	contextKind
)
	if not (unit and st and C_UnitAuras) then return end
	resetAuraCache(getAuraCache(st, "buff"))
	resetAuraCache(getAuraCache(st, "debuff"))
	clearAuraKinds(st)
	local flagsById = st._auraKindById
	local seen = {}
	local helpfulScanFilter = helpfulFilter
	if wantsHealerBuffPlacement then
		if healerBuffNeedsWideScan then
			helpfulScanFilter = "HELPFUL|INCLUDE_NAME_PLATE_ONLY"
		else
			helpfulScanFilter = "HELPFUL|INCLUDE_NAME_PLATE_ONLY|PLAYER"
		end
	elseif type(helpfulFilter) == "table" then
		helpfulScanFilter = "HELPFUL|INCLUDE_NAME_PLATE_ONLY"
	end

	local function storeAura(aura)
		local auraId = aura and aura.auraInstanceID
		if not auraId or seen[auraId] then return end
		seen[auraId] = true
		local flags = getAuraKindFlags(unit, aura, helpfulFilter, harmfulFilter, externalFilter, dispelFilter, wantBuff, wantDebuff, wantExternals, wantsDispel, wantsHealerBuffPlacement, contextKind)
		cacheAuraWithFlags(st, flagsById, aura, flags)
	end

	if wantBuff and helpfulScanFilter then
		local helpfulSlots, helpfulSlotCount = queryAuraSlots(unit, helpfulScanFilter, queryMax and queryMax.helpful)
		for i = 2, helpfulSlotCount do
			local aura = C_UnitAuras.GetAuraDataBySlot(unit, helpfulSlots[i])
			if aura then storeAura(aura) end
		end
	end
	if (wantDebuff or wantsDispel) and harmfulScanFilter then
		local harmfulSlots, harmfulSlotCount = queryAuraSlots(unit, harmfulScanFilter, queryMax and queryMax.harmful)
		for i = 2, harmfulSlotCount do
			local aura = C_UnitAuras.GetAuraDataBySlot(unit, harmfulSlots[i])
			if aura then storeAura(aura) end
		end
	end
	if wantExternals and externalFilter then
		local externalSlots, externalSlotCount = queryAuraSlots(unit, externalFilter, queryMax and queryMax.external)
		for i = 2, externalSlotCount do
			local aura = C_UnitAuras.GetAuraDataBySlot(unit, externalSlots[i])
			if aura then storeAura(aura) end
		end
	end
end

local function updateGroupAuraCache(
	unit,
	st,
	updateInfo,
	helpfulFilter,
	harmfulFilter,
	externalFilter,
	dispelFilter,
	wantBuff,
	wantDebuff,
	wantExternals,
	wantsDispel,
	wantsHealerBuffPlacement,
	contextKind
)
	if not (unit and st and updateInfo) then return end

	local wantsAny = wantBuff or wantDebuff or wantExternals or wantsDispel
	local buffCache = getAuraCache(st, "buff")
	local debuffCache = getAuraCache(st, "debuff")

	st._auraKindById = st._auraKindById or {}
	local flagsById = st._auraKindById

	if not wantsAny then
		resetAuraCache(buffCache)
		resetAuraCache(debuffCache)
		clearAuraKinds(st)
		return
	end

	if updateInfo.removedAuraInstanceIDs then
		for i = 1, #updateInfo.removedAuraInstanceIDs do
			local auraId = updateInfo.removedAuraInstanceIDs[i]
			if auraId and (buffCache.auras[auraId] or debuffCache.auras[auraId] or (flagsById and flagsById[auraId] ~= nil)) then
				markDispelAuraDirty(st, auraId)
				removeAuraFromGroupStore(buffCache, nil, auraId)
				removeAuraFromGroupStore(debuffCache, nil, auraId)
				if flagsById then flagsById[auraId] = nil end
			end
		end
	end

	if updateInfo.addedAuras then
		for i = 1, #updateInfo.addedAuras do
			local aura = updateInfo.addedAuras[i]
			local flags =
				getAuraKindFlags(unit, aura, helpfulFilter, harmfulFilter, externalFilter, dispelFilter, wantBuff, wantDebuff, wantExternals, wantsDispel, wantsHealerBuffPlacement, contextKind)
			cacheAuraWithFlags(st, flagsById, aura, flags)
		end
	end

	if updateInfo.updatedAuraInstanceIDs and C_UnitAuras and C_UnitAuras.GetAuraDataByAuraInstanceID then
		for i = 1, #updateInfo.updatedAuraInstanceIDs do
			local auraId = updateInfo.updatedAuraInstanceIDs[i]
			if auraId then
				local cachedFlags = flagsById and flagsById[auraId]
				local wasKnown = buffCache.auras[auraId] or debuffCache.auras[auraId] or cachedFlags
				if wasKnown then
					local aura = C_UnitAuras.GetAuraDataByAuraInstanceID(unit, auraId)
					if aura then
						local flags = getAuraKindFlags(
							unit,
							aura,
							helpfulFilter,
							harmfulFilter,
							externalFilter,
							dispelFilter,
							wantBuff,
							wantDebuff,
							wantExternals,
							wantsDispel,
							wantsHealerBuffPlacement,
							contextKind
						)
						cacheAuraWithFlags(st, flagsById, aura, flags)
					elseif wasKnown then
						markDispelAuraDirty(st, auraId)
						removeAuraFromGroupStore(buffCache, nil, auraId)
						removeAuraFromGroupStore(debuffCache, nil, auraId)
						if flagsById then flagsById[auraId] = nil end
					end
				end
			end
		end
	end

	compactAuraOrder(buffCache)
	compactAuraOrder(debuffCache)
end

function GF:UpdateAuras(self, updateInfo)
	local st = getState(self)
	if not (st and AuraUtil) then return end
	local unit = getUnit(self)
	local inEditMode = isEditModeActive()
	if inEditMode then
		if GF and (GF._editModeSampleAuras == false or GF._editModeExiting == true) then
			if st.buffContainer then st.buffContainer:Hide() end
			if st.debuffContainer then st.debuffContainer:Hide() end
			if st.externalContainer then st.externalContainer:Hide() end
			GF:ClearBlizzardAuraContainer(self)
			hideAuraButtons(st.buffButtons, 1)
			hideAuraButtons(st.debuffButtons, 1)
			hideAuraButtons(st.externalButtons, 1)
			st._auraSampleActive = nil
			GF:UpdateDispelTint(self, nil, nil)
			if st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then UF.GroupFramesHealerBuffs.ClearButton(self) end
			st._healerBuffPlacementActive = nil
			return
		end
		GF:UpdateSampleAuras(self)
		return
	elseif self._eqolPreview then
		if st.buffContainer then st.buffContainer:Hide() end
		if st.debuffContainer then st.debuffContainer:Hide() end
		if st.externalContainer then st.externalContainer:Hide() end
		hideAuraButtons(st.buffButtons, 1)
		hideAuraButtons(st.debuffButtons, 1)
		hideAuraButtons(st.externalButtons, 1)
		st._auraSampleActive = nil
		GF:UpdateDispelTint(self, nil, nil)
		if st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then UF.GroupFramesHealerBuffs.ClearButton(self) end
		st._healerBuffPlacementActive = nil
		return
	end
	if st._auraSampleActive then
		if st.buffContainer then st.buffContainer:Hide() end
		if st.debuffContainer then st.debuffContainer:Hide() end
		if st.externalContainer then st.externalContainer:Hide() end
		hideAuraButtons(st.buffButtons, 1)
		hideAuraButtons(st.debuffButtons, 1)
		hideAuraButtons(st.externalButtons, 1)
		st._auraSampleActive = nil
		updateInfo = nil
	end
	if not (unit and C_UnitAuras) then
		GF:UpdateDispelTint(self, nil, nil)
		if st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then UF.GroupFramesHealerBuffs.ClearButton(self) end
		st._healerBuffPlacementActive = nil
		return
	end
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local def = DEFAULTS[self._eqolGroupKind or "party"]
	local blizzardRenderer = GF.IsGroupBlizzardAuraRenderer(cfg, def)
	local blizzardBuffs = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "buffs")
	local blizzardDebuffs = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "debuffs")
	local blizzardDispels = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "dispels")
	local blizzardExternals = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "externals")
	if blizzardRenderer then
		local blizzardContainerReady = st.blizzardAuras and st.blizzardAuras._eqolBlizzardAuraAnchorID
		if not updateInfo or updateInfo.isFullUpdate or not blizzardContainerReady then GF:UpdateBlizzardAuraContainer(self) end
		if blizzardDispels then GF:ClearPrivateAuraDispelContainer(self) end
	elseif st.blizzardAuras then
		GF:ClearBlizzardAuraContainer(self)
	end
	if not blizzardDispels then GF:UpdatePrivateAuraDispelContainer(self) end
	local ac = (cfg and cfg.auras) or EMPTY
	if cfg then GFH.SyncAurasEnabled(cfg) end
	local wantsAuras = st._wantsAuras
	if wantsAuras == nil then wantsAuras = ((ac.buff and ac.buff.enabled) or (ac.debuff and ac.debuff.enabled) or (ac.externals and ac.externals.enabled)) or false end
	local wantsDispelTint = st._wantsDispelTint == true and not blizzardDispels
	local wantsHealerBuffPlacement = st._wantsHealerBuffPlacement == true
	if wantsHealerBuffPlacement and not (cfg and cfg.healerBuffPlacement and cfg.healerBuffPlacement.enabled == true) then
		wantsHealerBuffPlacement = false
		st._wantsHealerBuffPlacement = false
	end
	if wantsHealerBuffPlacement and blizzardBuffs then
		wantsHealerBuffPlacement = false
		st._wantsHealerBuffPlacement = false
		if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then UF.GroupFramesHealerBuffs.ClearButton(self) end
		st._healerBuffPlacementActive = nil
	end
	if wantsAuras == false and not wantsDispelTint and not wantsHealerBuffPlacement then
		if st.buffContainer then st.buffContainer:Hide() end
		if st.debuffContainer then st.debuffContainer:Hide() end
		if st.externalContainer then st.externalContainer:Hide() end
		hideAuraButtons(st.buffButtons, 1)
		hideAuraButtons(st.debuffButtons, 1)
		hideAuraButtons(st.externalButtons, 1)
		if st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then UF.GroupFramesHealerBuffs.ClearButton(self) end
		st._healerBuffPlacementActive = nil
		return
	end
	if wantsAuras == false then
		if st.buffContainer then st.buffContainer:Hide() end
		if st.debuffContainer then st.debuffContainer:Hide() end
		if st.externalContainer then st.externalContainer:Hide() end
		hideAuraButtons(st.buffButtons, 1)
		hideAuraButtons(st.debuffButtons, 1)
		hideAuraButtons(st.externalButtons, 1)
	end

	st._auraSampleActive = nil

	local wantBuff = ((wantsAuras and ac.buff and ac.buff.enabled ~= false) or wantsHealerBuffPlacement) and not blizzardBuffs
	local wantDebuff = wantsAuras and ac.debuff and ac.debuff.enabled ~= false and not blizzardDebuffs
	local wantExternals = wantsAuras and ac.externals and ac.externals.enabled ~= false and not blizzardExternals
	if blizzardBuffs then
		if st.buffContainer then st.buffContainer:Hide() end
		hideAuraButtons(st.buffButtons, 1)
	end
	if blizzardDebuffs then
		if st.debuffContainer then st.debuffContainer:Hide() end
		hideAuraButtons(st.debuffButtons, 1)
	end
	if blizzardExternals then
		if st.externalContainer then st.externalContainer:Hide() end
		hideAuraButtons(st.externalButtons, 1)
	end
	if not (wantBuff or wantDebuff or wantExternals) and not wantsDispelTint and not wantsHealerBuffPlacement then return end
	if wantsAuras then
		if
			not st._auraLayout
			or (wantBuff and not (st._auraLayout.buff and st._auraLayout.buff.key))
			or (wantDebuff and not (st._auraLayout.debuff and st._auraLayout.debuff.key))
			or (wantExternals and not (st._auraLayout.externals and st._auraLayout.externals.key))
		then
			GF:LayoutAuras(self)
		end
	end
	local helpfulFilter = GF.GetBuffHelpfulFilter(ac)
	local harmfulFilter = getGroupDebuffMatchFilter(ac.debuff)
	local harmfulScanFilter = AURA_FILTERS.harmful
	local dispelFilter = AURA_FILTERS.dispellable
	local externalFilter = AURA_FILTERS.bigDefensive
	local auraContextKind = self and (self._eqolGroupKind or "party") or "party"
	local auraQueryMax = st._auraQueryMax
	if not auraQueryMax then
		auraQueryMax = {}
		st._auraQueryMax = auraQueryMax
	end
	local function normalizeMax(value)
		value = floor(tonumber(value) or 0)
		if value < 1 then return nil end
		return value
	end
	local buffMax = normalizeMax(st._auraLayout and st._auraLayout.buff and st._auraLayout.buff.maxCount)
	local debuffMax = normalizeMax(st._auraLayout and st._auraLayout.debuff and st._auraLayout.debuff.maxCount)
	local externalMax = normalizeMax(st._auraLayout and st._auraLayout.externals and st._auraLayout.externals.maxCount)
	local helpfulNeedsWideScan = type(helpfulFilter) == "table"
	if wantsHealerBuffPlacement then
		-- Healer buff placement needs the full helpful aura set; capped scans can miss tracked spells.
		auraQueryMax.helpful = nil
	elseif helpfulNeedsWideScan then
		auraQueryMax.helpful = nil
	elseif wantBuff and buffMax then
		local extra = (wantExternals and externalMax) or 0
		local helpfulMax = normalizeMax(buffMax + extra)
		auraQueryMax.helpful = helpfulMax or buffMax
	else
		auraQueryMax.helpful = nil
	end
	local scanHarmful = wantDebuff or wantsDispelTint
	if scanHarmful then
		local harmfulNeedsWideScan = wantsDispelTint or type(harmfulFilter) == "table"
		auraQueryMax.harmful = harmfulNeedsWideScan and nil or debuffMax
	else
		auraQueryMax.harmful = nil
	end
	auraQueryMax.external = wantExternals and externalMax or nil
	local healerBuffCompiled = nil
	local healerBuffNeedsWideScan = false
	if wantsHealerBuffPlacement and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.GetCompiled then
		healerBuffCompiled = UF.GroupFramesHealerBuffs.GetCompiled(self._eqolGroupKind or "party", cfg)
		if healerBuffCompiled and UF.GroupFramesHealerBuffs.CompiledNeedsWideHelpfulScan then healerBuffNeedsWideScan = UF.GroupFramesHealerBuffs.CompiledNeedsWideHelpfulScan(healerBuffCompiled) end
	end
	local buffCache = getAuraCache(st, "buff")
	local debuffCache = getAuraCache(st, "debuff")
	st._auraKindById = st._auraKindById or {}
	if not updateInfo or updateInfo.isFullUpdate then
		fullScanGroupAuras(
			unit,
			st,
			helpfulFilter,
			harmfulFilter,
			harmfulScanFilter,
			externalFilter,
			dispelFilter,
			wantBuff,
			wantDebuff,
			wantExternals,
			wantsDispelTint,
			wantsHealerBuffPlacement,
			healerBuffNeedsWideScan,
			auraQueryMax,
			auraContextKind
		)
		if wantsAuras then
			if wantBuff then updateAuraType(self, unit, st, ac, "buff", buffCache, nil, healerBuffCompiled) end
			if wantDebuff then updateAuraType(self, unit, st, ac, "debuff", debuffCache, nil, healerBuffCompiled) end
			if wantExternals then updateAuraType(self, unit, st, ac, "externals", buffCache, nil, healerBuffCompiled) end
		end
		if wantsDispelTint then
			GF:UpdateDispelTint(self, debuffCache, dispelFilter, nil, AURA_KIND_DISPEL)
		else
			GF:UpdateDispelTint(self, nil, nil)
		end
		if wantsHealerBuffPlacement and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.UpdateFromAuras then
			UF.GroupFramesHealerBuffs.UpdateFromAuras(self, updateInfo, buffCache, nil, true, healerBuffCompiled)
			st._healerBuffPlacementActive = true
		elseif st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then
			UF.GroupFramesHealerBuffs.ClearButton(self)
			st._healerBuffPlacementActive = nil
		end
		return
	end
	local touchBuff, touchDebuff, touchExternals, touchDispel, touchHealer
	local function markKinds(flags)
		if not flags then return end
		if hasAuraFlag(flags, AURA_KIND_HELPFUL) then touchBuff = true end
		if hasAuraFlag(flags, AURA_KIND_HARMFUL) then touchDebuff = true end
		if hasAuraFlag(flags, AURA_KIND_EXTERNAL) then touchExternals = true end
		if hasAuraFlag(flags, AURA_KIND_DISPEL) then touchDispel = true end
		if hasAuraFlag(flags, 16) then touchHealer = true end
	end
	if updateInfo then
		local preFlags = st._auraKindById
		local removed = updateInfo.removedAuraInstanceIDs
		local updated = updateInfo.updatedAuraInstanceIDs
		if removed or updated then
			if preFlags then
				if removed then
					for i = 1, #removed do
						local auraId = removed[i]
						local flags = preFlags[auraId]
						if flags then
							markKinds(flags)
						elseif auraId then
							if buffCache.auras[auraId] then
								touchBuff = wantBuff
								touchExternals = wantExternals
								touchHealer = wantsHealerBuffPlacement
							end
							if debuffCache.auras[auraId] then
								touchDebuff = wantDebuff
								touchDispel = wantsDispelTint
							end
						end
					end
				end
				if updated then
					for i = 1, #updated do
						local auraId = updated[i]
						local flags = preFlags[auraId]
						if flags then
							markKinds(flags)
						elseif auraId then
							if buffCache.auras[auraId] then
								touchBuff = wantBuff
								touchExternals = wantExternals
								touchHealer = wantsHealerBuffPlacement
							end
							if debuffCache.auras[auraId] then
								touchDebuff = wantDebuff
								touchDispel = wantsDispelTint
							end
						end
					end
				end
			else
				touchBuff = wantBuff
				touchDebuff = wantDebuff
				touchExternals = wantExternals
				touchDispel = wantsDispelTint
				touchHealer = wantsHealerBuffPlacement
			end
		end
	end

	updateGroupAuraCache(
		unit,
		st,
		updateInfo,
		helpfulFilter,
		harmfulFilter,
		externalFilter,
		dispelFilter,
		wantBuff,
		wantDebuff,
		wantExternals,
		wantsDispelTint,
		wantsHealerBuffPlacement,
		auraContextKind
	)
	local changed = st._auraChanged
	if updateInfo then
		if not changed then
			changed = {}
			st._auraChanged = changed
		elseif wipe then
			wipe(changed)
		else
			for k in pairs(changed) do
				changed[k] = nil
			end
		end
		local added = updateInfo.addedAuras
		if added then
			for i = 1, #added do
				local aura = added[i]
				local id = aura and aura.auraInstanceID
				if id then changed[id] = true end
			end
		end
		local updated = updateInfo.updatedAuraInstanceIDs
		if updated then
			for i = 1, #updated do
				local id = updated[i]
				if id then changed[id] = true end
			end
		end
		local flags = st._auraKindById
		if not flags then
			if not (touchBuff or touchDebuff or touchExternals or touchDispel or touchHealer) then
				touchBuff = wantBuff
				touchDebuff = wantDebuff
				touchExternals = wantExternals
				touchDispel = wantsDispelTint
				touchHealer = wantsHealerBuffPlacement
			end
		else
			if added then
				for i = 1, #added do
					local aura = added[i]
					local id = aura and aura.auraInstanceID
					if id then markKinds(flags[id]) end
				end
			end
			if updated then
				for i = 1, #updated do
					local id = updated[i]
					if id then markKinds(flags[id]) end
				end
			end
		end
	end
	if wantsAuras then
		if wantBuff and touchBuff then updateAuraType(self, unit, st, ac, "buff", buffCache, changed, healerBuffCompiled) end
		if wantDebuff and touchDebuff then updateAuraType(self, unit, st, ac, "debuff", debuffCache, changed, healerBuffCompiled) end
		if wantExternals and touchExternals then updateAuraType(self, unit, st, ac, "externals", buffCache, changed, healerBuffCompiled) end
	end
	if wantsDispelTint then
		if touchDispel then GF:UpdateDispelTint(self, debuffCache, dispelFilter, nil, AURA_KIND_DISPEL) end
	elseif st._dispelTintShown ~= false or st._dispelGlowActive or st._dispelAuraId or st._dispelAuraIdDirty then
		GF:UpdateDispelTint(self, nil, nil)
	end
	if touchHealer and wantsHealerBuffPlacement and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.UpdateFromAuras then
		UF.GroupFramesHealerBuffs.UpdateFromAuras(self, updateInfo, buffCache, changed, false, healerBuffCompiled)
		st._healerBuffPlacementActive = true
	elseif not wantsHealerBuffPlacement and st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then
		UF.GroupFramesHealerBuffs.ClearButton(self)
		st._healerBuffPlacementActive = nil
	end
end

function GF:UpdateSampleAuras(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (st and AuraUtil) then return end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local def = DEFAULTS[kind] or EMPTY
	local blizzardRenderer = GF.IsGroupBlizzardAuraRenderer(cfg, def)
	local blizzardBuffs = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "buffs")
	local blizzardDebuffs = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "debuffs")
	local blizzardDispels = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "dispels")
	local blizzardExternals = blizzardRenderer and GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "externals")
	if blizzardRenderer then
		GF:UpdateBlizzardAuraContainer(self)
		if blizzardDispels then GF:ClearPrivateAuraDispelContainer(self) end
	elseif st.blizzardAuras and UFHelper and UFHelper.RemovePrivateAuras then
		UFHelper.RemovePrivateAuras(st.blizzardAuras)
		if st.blizzardAuras.Hide then st.blizzardAuras:Hide() end
	end
	if not blizzardDispels then GF:UpdatePrivateAuraDispelContainer(self) end
	local ac = (cfg and cfg.auras) or EMPTY
	local scfg = (cfg and cfg.status) or EMPTY
	local wantsDispelTint = resolveDispelIndicatorEnabled(cfg, kind) and not blizzardDispels
	st._wantsDispelTint = wantsDispelTint
	local wantsHealerBuffPlacement = st._wantsHealerBuffPlacement == true
	if wantsHealerBuffPlacement and not (cfg and cfg.healerBuffPlacement and cfg.healerBuffPlacement.enabled == true) then
		wantsHealerBuffPlacement = false
		st._wantsHealerBuffPlacement = false
	end
	if wantsHealerBuffPlacement and blizzardBuffs then
		wantsHealerBuffPlacement = false
		st._wantsHealerBuffPlacement = false
		if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then UF.GroupFramesHealerBuffs.ClearButton(self) end
		st._healerBuffPlacementActive = nil
	end
	if cfg then GFH.SyncAurasEnabled(cfg) end
	local rawWantsAuras = ((ac.buff and ac.buff.enabled) or (ac.debuff and ac.debuff.enabled) or (ac.externals and ac.externals.enabled)) or false
	if ac.enabled == true then rawWantsAuras = true end
	st._wantsAuras = rawWantsAuras
	local wantBuff = ac.buff and ac.buff.enabled ~= false and not blizzardBuffs
	local wantDebuff = ac.debuff and ac.debuff.enabled ~= false and not blizzardDebuffs
	local wantExternals = ac.externals and ac.externals.enabled ~= false and not blizzardExternals
	if wantsHealerBuffPlacement and not blizzardBuffs then wantBuff = true end
	local wantsCustomAuras = wantBuff or wantDebuff or wantExternals
	if rawWantsAuras == false or (not wantsCustomAuras and not wantsDispelTint) then
		if st.buffContainer then st.buffContainer:Hide() end
		if st.debuffContainer then st.debuffContainer:Hide() end
		if st.externalContainer then st.externalContainer:Hide() end
		hideAuraButtons(st.buffButtons, 1)
		hideAuraButtons(st.debuffButtons, 1)
		hideAuraButtons(st.externalButtons, 1)
		st._auraSampleActive = nil
		if wantsDispelTint then
			GF:UpdateDispelTint(self, nil, nil, true)
		else
			GF:UpdateDispelTint(self, nil, nil)
		end
		if UF.GroupFramesHealerBuffs and st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs.ClearButton then
			UF.GroupFramesHealerBuffs.ClearButton(self)
			st._healerBuffPlacementActive = nil
		end
		return
	end
	if
		not st._auraLayout
		or (wantBuff and not (st._auraLayout.buff and st._auraLayout.buff.key))
		or (wantDebuff and not (st._auraLayout.debuff and st._auraLayout.debuff.key))
		or (wantExternals and not (st._auraLayout.externals and st._auraLayout.externals.key))
	then
		GF:LayoutAuras(self)
	end

	local function updateSampleType(kindKey)
		local meta = AURA_TYPE_META[kindKey]
		if not meta then return end
		local typeCfg = (ac and ac[kindKey]) or EMPTY
		if typeCfg.enabled == false or (kindKey == "buff" and not wantBuff) or (kindKey == "debuff" and not wantDebuff) or (kindKey == "externals" and not wantExternals) then
			local container = st[meta.containerKey]
			if container then container:Hide() end
			hideAuraButtons(st[meta.buttonsKey], 1)
			return
		end

		local layout = st._auraLayout and st._auraLayout[kindKey]
		local style = st._auraStyle and st._auraStyle[kindKey]
		if not (layout and style) then return end

		local container = ensureAuraContainer(st, meta.containerKey)
		if not container then return end
		container:Show()

		local buttons = st[meta.buttonsKey]
		if not buttons then
			buttons = {}
			st[meta.buttonsKey] = buttons
		end

		local iconList = SAMPLE_BUFF_ICONS
		if kindKey == "debuff" then
			iconList = SAMPLE_DEBUFF_ICONS
		elseif kindKey == "externals" then
			iconList = SAMPLE_EXTERNAL_ICONS
		end
		local maxCount = layout.maxCount or 0
		local shown = math.min(maxCount, #iconList)
		local now = GetTime and GetTime() or 0
		local sampleStyle = getSampleStyle(st, kindKey, style)
		sampleStyle.tooltipUseEditMode = st._tooltipUseEditMode == true
		sampleStyle.tooltipAnchor = "ANCHOR_RIGHT"
		local unitToken = unit or "player"
		for i = 1, shown do
			local aura = getSampleAuraData(kindKey, i, now)
			local btn = AuraUtil.ensureAuraButton(container, buttons, i, sampleStyle)
			AuraUtil.applyAuraToButton(btn, aura, sampleStyle, meta.isDebuff, unitToken)
			if kindKey == "externals" then GF.ApplyExternalGlow(btn, sampleStyle) end
			setAuraTooltipState(btn, sampleStyle)
			if btn._auraLayoutKey ~= layout.key then
				positionAuraButton(btn, container, layout.primary, layout.secondary, i, layout.perRow, layout.size, layout.spacing)
				btn._auraLayoutKey = layout.key
			end
			btn:Show()
		end
		updateAuraContainerSize(container, layout.anchorPoint, shown, layout.maxCount or shown, layout.perRow, layout.size, layout.spacing, layout.primary)
		hideAuraButtons(buttons, shown + 1)
	end

	updateSampleType("buff")
	updateSampleType("debuff")
	updateSampleType("externals")
	if wantsDispelTint then
		GF:UpdateDispelTint(self, nil, nil, true)
	else
		GF:UpdateDispelTint(self, nil, nil)
	end
	if UF.GroupFramesHealerBuffs and st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs.ClearButton then
		UF.GroupFramesHealerBuffs.ClearButton(self)
		st._healerBuffPlacementActive = nil
	end
	st._auraSampleActive = true
end

function GF:UpdateName(self)
	local unit = getUnit(self)
	local st = getState(self)
	local fs = st and (st.nameText or st.name)
	if not (unit and st and fs) then return end
	if st._wantsName == false then
		if fs.SetText then fs:SetText("") end
		if fs.SetShown then fs:SetShown(false) end
		st._lastName = nil
		return
	end
	if fs.SetShown then fs:SetShown(true) end
	if UnitExists and not UnitExists(unit) then
		fs:SetText("")
		st._lastName = nil
		return
	end
	local name = UnitName and UnitName(unit) or ""
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local tc = cfg and cfg.text or {}
	local sc = cfg and cfg.status or {}
	local connected = UnitIsConnected and GFH.UnsecretBool(UnitIsConnected(unit))
	local displayName = name or ""
	if isEditModeActive() and self._eqolPreview and st._previewName then displayName = st._previewName end
	local maxChars = tonumber(tc.nameMaxChars) or 0
	local noEllipsis = tc.nameNoEllipsis
	if noEllipsis == nil then noEllipsis = (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameNoEllipsis) == true end
	if noEllipsis and maxChars > 0 and UFHelper and UFHelper.getNameLimitWidth and UFHelper.truncateTextToWidth then
		local hc = cfg and cfg.health or {}
		local font = tc.font or hc.font
		local fontSize = GF.ScaleContentValue(self, tc.fontSize or hc.fontSize or 12, cfg, 1)
		local fontOutline = tc.fontOutline or hc.fontOutline or "OUTLINE"
		local maxWidth = UFHelper.getNameLimitWidth(font, fontSize, fontOutline, maxChars)
		if maxWidth and maxWidth > 0 then displayName = UFHelper.truncateTextToWidth(font, fontSize, fontOutline, displayName, maxWidth) end
	end
	if connected == false then displayName = (displayName and displayName ~= "") and (displayName .. " |cffff6666DC|r") or "|cffff6666DC|r" end
	displayName = displayName or ""
	if st._lastName ~= displayName then
		fs:SetText(displayName)
		st._lastName = displayName
	end

	local r, g, b, a = 1, 1, 1, 1
	local nameMode = sc.nameColorMode
	if nameMode == nil then nameMode = (tc.useClassColor ~= false) and "CLASS" or "CUSTOM" end
	if nameMode == "CUSTOM" then
		r, g, b, a = unpackColor(sc.nameColor, GFH.COLOR_WHITE)
	elseif nameMode == "CLASS" then
		if not st._classR then GF:EnsureUnitClassColor(self, st, unit) end
		if st._classR then
			r, g, b, a = st._classR, st._classG, st._classB, st._classA or 1
		end
	end
	if connected == false then
		r, g, b, a = 0.7, 0.7, 0.7, 1
	end
	if st._lastNameR ~= r or st._lastNameG ~= g or st._lastNameB ~= b or st._lastNameA ~= a then
		st._lastNameR, st._lastNameG, st._lastNameB, st._lastNameA = r, g, b, a
		if fs.SetTextColor then fs:SetTextColor(r, g, b, a) end
	end
end

local function shouldShowLevel(scfg, unit)
	if not scfg or scfg.levelEnabled == false then return false end
	if scfg.hideLevelAtMax and addon.variables and addon.variables.isMaxLevel and UnitLevel then
		local level = UnitLevel(unit)
		if issecretvalue and issecretvalue(level) then return true end
		level = tonumber(level) or 0
		if level > 0 and addon.variables.isMaxLevel[level] then return false end
	end
	return true
end

local function getRaidSubgroupForUnit(unit)
	if not unit then return nil end
	local idx
	if UnitInRaid then idx = UnitInRaid(unit) end
	if (not idx) and type(unit) == "string" then
		local raidIndex = unit:match("^raid(%d+)$")
		if raidIndex then idx = tonumber(raidIndex) end
	end
	if not idx then return nil end
	if not GetRaidRosterInfo then return nil end
	local _, _, subgroup = GetRaidRosterInfo(idx)
	if issecretvalue and issecretvalue(subgroup) then return nil end
	return tonumber(subgroup)
end

function GF:UpdateLevel(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (st and st.levelText) then return end
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local scfg = cfg and cfg.status or {}
	local enabled = scfg.levelEnabled ~= false
	local show = enabled and unit and shouldShowLevel(scfg, unit)
	if not show and isEditModeActive() and enabled then show = true end
	st.levelText:SetShown(show)
	if not show then return end

	local levelText = "??"
	if unit and UnitExists and UnitExists(unit) then
		levelText = getSafeLevelText(unit, false)
	elseif isEditModeActive() then
		levelText = tostring(scfg.sampleLevel or 60)
	end
	st.levelText:SetText(levelText)

	local r, g, b, a = 1, 0.85, 0, 1
	if scfg.levelColorMode == "CUSTOM" then
		r, g, b, a = unpackColor(scfg.levelColor, GFH.COLOR_LEVEL)
	elseif scfg.levelColorMode == "CLASS" then
		if not st._classR then GF:EnsureUnitClassColor(self, st, unit) end
		if st._classR then
			r, g, b, a = st._classR, st._classG, st._classB, st._classA or 1
		end
	end
	if st._lastLevelR ~= r or st._lastLevelG ~= g or st._lastLevelB ~= b or st._lastLevelA ~= a then
		st._lastLevelR, st._lastLevelG, st._lastLevelB, st._lastLevelA = r, g, b, a
		st.levelText:SetTextColor(r, g, b, a)
	end
end

function GF:UpdateStatusText(self)
	local st = getState(self)
	local unit = getUnit(self)
	if not st then return end
	local statusFs = st.statusText
	local groupFs = st.groupNumberText
	if not (statusFs or groupFs) then return end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local def = DEFAULTS[kind] or {}
	local scfg = cfg and cfg.status or {}
	local us = scfg.unitStatus or {}
	local hc = cfg and cfg.health or {}
	if st._wantsStatusText == false or us.enabled == false then
		if statusFs then
			statusFs:SetText("")
			statusFs:Hide()
		end
		if groupFs then
			groupFs:SetText("")
			groupFs:Hide()
		end
		return
	end
	local inEditMode = isEditModeActive()
	if inEditMode and GF and GF._editModeSampleStatusText == false then
		if statusFs then
			statusFs:SetText("")
			statusFs:Hide()
		end
		if groupFs then
			groupFs:SetText("")
			groupFs:Hide()
		end
		return
	end
	local allowSample = inEditMode and self._eqolPreview
	if UnitExists and unit and not UnitExists(unit) and not allowSample then
		if statusFs then
			statusFs:SetText("")
			statusFs:Hide()
		end
		if groupFs then
			groupFs:SetText("")
			groupFs:Hide()
		end
		return
	end
	local statusTag
	local showOffline = us.showOffline
	if showOffline == nil then showOffline = true end
	local showAFK = us.showAFK == true
	local showDead = us.showDead
	if showDead == nil then showDead = true end
	local showDND = us.showDND == true
	local showGhost = us.showGhost
	if showGhost == nil then showGhost = true end
	local connected = unit and UnitIsConnected and GFH.UnsecretBool(UnitIsConnected(unit)) or nil
	local isAFK = unit and UnitIsAFK and GFH.UnsecretBool(UnitIsAFK(unit)) or nil
	local isDND = unit and UnitIsDND and GFH.UnsecretBool(UnitIsDND(unit)) or nil
	local isDead = unit and UnitIsDeadOrGhost and GFH.UnsecretBool(UnitIsDeadOrGhost(unit)) or nil
	local isGhost = unit and UnitIsGhost and GFH.UnsecretBool(UnitIsGhost(unit)) or nil
	if connected == false then
		if showOffline then statusTag = PLAYER_OFFLINE or "Offline" end
	elseif isDead == true then
		if isGhost == true then
			if showGhost then statusTag = GHOST or "Ghost" end
		elseif showDead then
			statusTag = DEAD or "Dead"
		end
	elseif isDND == true then
		if showDND then statusTag = DEFAULT_DND_MESSAGE or "DND" end
	end
	if not statusTag and isAFK == true then
		if showAFK then statusTag = DEFAULT_AFK_MESSAGE or "AFK" end
	end

	local groupTag
	if resolveGroupNumberEnabled(cfg, def) then
		local subgroup = getRaidSubgroupForUnit(unit)
		if not subgroup and allowSample then subgroup = st._previewGroup or 1 end
		if subgroup then groupTag = formatGroupNumber(subgroup, resolveGroupNumberFormat(cfg, def)) end
	end
	if not statusTag and allowSample then
		if showOffline then statusTag = PLAYER_OFFLINE or "Offline" end
		if not statusTag and showDead then statusTag = DEAD or "Dead" end
		if not statusTag and showGhost then statusTag = GHOST or "Ghost" end
		if not statusTag and showDND then statusTag = DEFAULT_DND_MESSAGE or "DND" end
		if not statusTag and showAFK then statusTag = DEFAULT_AFK_MESSAGE or "AFK" end
	end
	local scale = GFH.GetEffectiveScale(self)
	if not scale or scale <= 0 then scale = (UIParent and UIParent.GetEffectiveScale and UIParent:GetEffectiveScale()) or 1 end
	local contentScale = GF.GetDynamicContentScale(self, cfg)

	if statusFs then
		if statusTag then
			local style = resolveStatusTextStyle(cfg, def, hc)
			GF.ApplyScaledFont(self, statusFs, style.font, style.fontSize or 12, style.fontOutline, cfg)
			applyStatusTextAnchor(st, style.anchor, GF.ScaleOffset(style.offset, contentScale), scale, GF.GetLayoutAnchorFrame(st, self) or self, statusFs)
			local r, g, b, a = unpackColor(style.color, GFH.COLOR_WHITE)
			statusFs:SetText(statusTag)
			statusFs:SetTextColor(r, g, b, a)
			statusFs:Show()
		else
			statusFs:SetText("")
			statusFs:Hide()
		end
	end

	if groupFs then
		if groupTag then
			local style = resolveGroupNumberStyle(cfg, def, hc)
			if UFHelper and UFHelper.applyFont then UFHelper.applyFont(groupFs, style.font, style.fontSize or 12, style.fontOutline) end
			applyStatusTextAnchor(st, style.anchor, style.offset, scale, GF.GetLayoutAnchorFrame(st, self) or self, groupFs)
			local r, g, b, a = unpackColor(style.color, GFH.COLOR_WHITE)
			groupFs:SetText(groupTag)
			groupFs:SetTextColor(r, g, b, a)
			groupFs:Show()
		else
			groupFs:SetText("")
			groupFs:Hide()
		end
	end
end

local function hideGroupIndicators(container)
	if not container then return end
	local overlay = container._eqolGroupIndicatorOverlay
	if overlay and overlay.Hide then overlay:Hide() end
	local indicators = container._eqolGroupIndicators
	if not indicators then return end
	for _, fs in pairs(indicators) do
		if fs then
			fs:SetText("")
			fs:Hide()
		end
	end
end

function GF.EnsureGroupIndicatorOverlay(container, target)
	if not (container and target and CreateFrame) then return container end

	local overlay = container._eqolGroupIndicatorOverlay
	if not overlay then
		overlay = CreateFrame("Frame", nil, container)
		overlay:EnableMouse(false)
		if overlay.SetAllPoints then overlay:SetAllPoints(container) end
		container._eqolGroupIndicatorOverlay = overlay
	end

	if overlay.GetParent and overlay:GetParent() ~= container then overlay:SetParent(container) end
	if overlay.SetAllPoints then overlay:SetAllPoints(container) end
	setFrameLevelAbove(overlay, target, 10)
	if overlay.Show then overlay:Show() end
	return overlay
end

local function updateGroupIndicatorsForFrames(container, frames, cfg, def, isPreview, fixedSubgroup)
	if not container then return end
	if not (cfg and resolveGroupIndicatorEnabled(cfg, def) and isGroupIndicatorAvailable(cfg, def)) then
		hideGroupIndicators(container)
		return
	end
	if not (frames and #frames > 0) then
		hideGroupIndicators(container)
		return
	end

	local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
	local anchorMode = tostring(style.anchor or "TOPLEFT"):upper()
	local candidates = {}
	local function pickCandidate(current, frame, key)
		if not frame then return current end
		local st = getState(frame)
		local anchorTarget = GF.GetLayoutAnchorFrame(st, frame) or frame
		if not anchorTarget then return current end

		local candidate = {
			frame = frame,
			key = key,
			anchorTarget = anchorTarget,
			left = anchorTarget.GetLeft and anchorTarget:GetLeft() or nil,
			right = anchorTarget.GetRight and anchorTarget:GetRight() or nil,
			top = anchorTarget.GetTop and anchorTarget:GetTop() or nil,
			bottom = anchorTarget.GetBottom and anchorTarget:GetBottom() or nil,
		}
		if not current then return candidate end
		if not (candidate.left and candidate.right and candidate.top and candidate.bottom and current.left and current.right and current.top and current.bottom) then
			if (key or math.huge) < (current.key or math.huge) then return candidate end
			return current
		end

		local epsilon = 0.5
		if anchorMode == "TOPRIGHT" then
			if candidate.right > current.right + epsilon then return candidate end
			if abs(candidate.right - current.right) <= epsilon and candidate.top > current.top + epsilon then return candidate end
		elseif anchorMode == "BOTTOMLEFT" then
			if candidate.left < current.left - epsilon then return candidate end
			if abs(candidate.left - current.left) <= epsilon and candidate.bottom < current.bottom - epsilon then return candidate end
		elseif anchorMode == "BOTTOMRIGHT" then
			if candidate.right > current.right + epsilon then return candidate end
			if abs(candidate.right - current.right) <= epsilon and candidate.bottom < current.bottom - epsilon then return candidate end
		elseif anchorMode == "TOP" then
			if candidate.top > current.top + epsilon then return candidate end
			if abs(candidate.top - current.top) <= epsilon and candidate.left < current.left - epsilon then return candidate end
		elseif anchorMode == "BOTTOM" then
			if candidate.bottom < current.bottom - epsilon then return candidate end
			if abs(candidate.bottom - current.bottom) <= epsilon and candidate.left < current.left - epsilon then return candidate end
		elseif anchorMode == "RIGHT" then
			if candidate.right > current.right + epsilon then return candidate end
			if abs(candidate.right - current.right) <= epsilon and candidate.top > current.top + epsilon then return candidate end
		elseif anchorMode == "CENTER" then
			if (key or math.huge) < (current.key or math.huge) then return candidate end
		else
			if candidate.left < current.left - epsilon then return candidate end
			if abs(candidate.left - current.left) <= epsilon and candidate.top > current.top + epsilon then return candidate end
		end

		return current
	end

	local fixedGroup = tonumber(fixedSubgroup)
	if fixedGroup and fixedGroup >= 1 and fixedGroup <= 8 then
		for visualIndex, frame in ipairs(frames) do
			if frame and frame.IsShown and frame:IsShown() then candidates[fixedGroup] = pickCandidate(candidates[fixedGroup], frame, visualIndex) end
		end
	else
		for visualIndex, frame in ipairs(frames) do
			if frame and frame.IsShown and frame:IsShown() then
				local st = getState(frame)
				local subgroup
				if isPreview then
					if st then subgroup = st._previewGroup end
				else
					local unit = getUnit(frame)
					if unit then subgroup = getRaidSubgroupForUnit(unit) end
				end

				if subgroup then
					subgroup = tonumber(subgroup) or subgroup
					candidates[subgroup] = pickCandidate(candidates[subgroup], frame, visualIndex)
				end
			end
		end
	end

	if not next(candidates) then
		hideGroupIndicators(container)
		return
	end

	local indicators = container._eqolGroupIndicators
	if not indicators then
		indicators = {}
		container._eqolGroupIndicators = indicators
	end
	local format = resolveGroupIndicatorFormat(cfg, def)
	local scale = GFH.GetEffectiveScale(container)
	if not scale or scale <= 0 then scale = (UIParent and UIParent.GetEffectiveScale and UIParent:GetEffectiveScale()) or 1 end
	local used = {}
	local overlayTarget

	for subgroup, entry in pairs(candidates) do
		local target = entry and entry.frame
		local anchorTarget = entry and entry.anchorTarget
		if anchorTarget then
			if not overlayTarget then
				overlayTarget = anchorTarget
			else
				local currentLevel = overlayTarget.GetFrameLevel and overlayTarget:GetFrameLevel() or 0
				local candidateLevel = anchorTarget.GetFrameLevel and anchorTarget:GetFrameLevel() or 0
				if candidateLevel > currentLevel then overlayTarget = anchorTarget end
			end
		end
	end

	local overlayParent = GF.EnsureGroupIndicatorOverlay(container, overlayTarget)

	for subgroup, entry in pairs(candidates) do
		local fs = indicators[subgroup]
		if not fs and overlayParent and overlayParent.CreateFontString then
			fs = overlayParent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
			indicators[subgroup] = fs
		end
		if fs and entry and entry.frame then
			local target = entry.frame
			local anchorTarget = entry.anchorTarget or target
			if anchorTarget then
				if fs.GetParent and fs:GetParent() ~= overlayParent then fs:SetParent(overlayParent) end
				if fs.SetDrawLayer then fs:SetDrawLayer("OVERLAY", 7) end
				local contentScale = GF.GetDynamicContentScale(target, cfg)
				local fontSize = GF.ScaleContentValue(target, style.fontSize or 12, cfg, 1)
				local offset = GF.ScaleOffset(style.offset, contentScale)
				if UFHelper and UFHelper.applyFont then UFHelper.applyFont(fs, style.font, fontSize, style.fontOutline) end
				applyGroupIndicatorAnchor(fs, style.anchor, offset, scale, anchorTarget)
				local r, g, b, a = unpackColor(style.color, GFH.COLOR_WHITE)
				fs:SetText(formatGroupNumber(subgroup, format))
				fs:SetTextColor(r, g, b, a)
				fs:Show()
				used[subgroup] = true
			else
				fs:SetText("")
				fs:Hide()
			end
		end
	end

	for subgroup, fs in pairs(indicators) do
		if not used[subgroup] then
			fs:SetText("")
			fs:Hide()
		end
	end
end

function GF:UpdateDispelTint(self, cache, dispelFilter, allowSample, requiredFlag)
	local st = getState(self)
	if not st then return end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local scfg = cfg and cfg.status or {}
	local dcfg = scfg.dispelTint or {}
	local defDispel = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
	if not allowSample and isEditModeActive() then
		local showSample = dcfg.showSample
		if showSample == nil then showSample = defDispel.showSample == true end
		allowSample = showSample == true
	end
	local overlayEnabled = dcfg.enabled
	if overlayEnabled == nil then overlayEnabled = defDispel.enabled ~= false end
	local glowEnabled = dcfg.glowEnabled
	if glowEnabled == nil then glowEnabled = defDispel.glowEnabled == true end
	if not overlayEnabled and not glowEnabled then
		hideDispelTint(st)
		GF.StopDispelGlow(st.barGroup or self, nil, st)
		GF:UpdatePrivateAuraDispelContainerVisibility(self)
		return
	end
	if allowSample then
		local showSample = dcfg.showSample
		if showSample == nil then showSample = defDispel.showSample == true end
		if not showSample then
			hideDispelTint(st)
			GF.StopDispelGlow(st.barGroup or self, nil, st)
			GF:UpdatePrivateAuraDispelContainerVisibility(self)
			return
		end
	end
	local alpha = dcfg.alpha
	if alpha == nil then alpha = defDispel.alpha or 0.25 end
	local fillEnabled = dcfg.fillEnabled
	if fillEnabled == nil then fillEnabled = defDispel.fillEnabled ~= false end
	local fillAlpha = dcfg.fillAlpha
	if fillAlpha == nil then fillAlpha = defDispel.fillAlpha or 0.2 end
	local fillColor = dcfg.fillColor or defDispel.fillColor or GFH.COLOR_BLACK
	local fr, fg, fb, fa = unpackColor(fillColor, GFH.COLOR_BLACK)
	if not fillEnabled then fillAlpha = 0 end
	local bgAlpha = fillAlpha * (fa or 1)

	local r, g, b
	if allowSample then
		r, g, b = GFH.GetDebuffColorFromName("Magic")
	else
		local unit = getUnit(self)
		if unit and cache and cache.order and cache.auras then
			local auras = cache.auras
			local order = cache.order
			local flagsById = st._auraKindById
			local dispelAuraId = st._dispelAuraId
			local dispelAura
			local needsScan = st._dispelAuraIdDirty or not dispelAuraId
			if not needsScan then
				dispelAura = auras[dispelAuraId]
				if not dispelAura then
					needsScan = true
				elseif requiredFlag and not hasAuraFlag(flagsById and flagsById[dispelAuraId], requiredFlag) then
					needsScan = true
				end
			end
			if needsScan then
				dispelAuraId = nil
				dispelAura = nil
				for i = 1, #order do
					local auraId = order[i]
					local aura = auraId and auras[auraId]
					local match = true
					if requiredFlag then
						local auraKey = (aura and aura.auraInstanceID) or auraId
						match = auraKey and hasAuraFlag(flagsById and flagsById[auraKey], requiredFlag)
					end
					if aura and match then
						dispelAura = aura
						dispelAuraId = aura.auraInstanceID or auraId
						break
					end
				end
				st._dispelAuraId = dispelAuraId
				st._dispelAuraIdDirty = nil
			end
			if dispelAura then
				local auraId = dispelAura.auraInstanceID or dispelAuraId
				if auraId and C_UnitAuras and C_UnitAuras.GetAuraDispelTypeColor and GFH and GFH.DispelColorCurve then
					local color = C_UnitAuras.GetAuraDispelTypeColor(unit, auraId, GFH.DispelColorCurve)
					if color then
						if color.GetRGBA then
							r, g, b = color:GetRGBA()
						elseif color.r then
							r, g, b = color.r, color.g, color.b
						end
					end
				end
				if not r then
					local dispelName = dispelAura.dispelName
					if not (issecretvalue and issecretvalue(dispelName)) and dispelName and dispelName ~= "" then
						r, g, b = GFH.GetDebuffColorFromName(dispelName)
					end
				end
			end
		end
	end

	if overlayEnabled then
		if r then
			applyDispelTint(st, r, g or 0, b or 0, alpha, fr, fg, fb, bgAlpha)
		else
			hideDispelTint(st)
		end
	else
		hideDispelTint(st)
	end

	if glowEnabled then
		GF:UpdateDispelGlow(self, r, g, b)
	else
		GF.StopDispelGlow(st.barGroup or self, nil, st)
	end
	GF:UpdatePrivateAuraDispelContainerVisibility(self)
end

function GF:UpdateDispelGlow(self, r, g, b)
	local st = getState(self)
	if not st then return end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local scfg = cfg and cfg.status or {}
	local dcfg = scfg.dispelTint or {}
	local defDispel = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
	local glowEnabled = dcfg.glowEnabled
	if glowEnabled == nil then glowEnabled = defDispel.glowEnabled == true end
	if not glowEnabled then
		GF.StopDispelGlow(st.barGroup or self, nil, st)
		return
	end
	if not (r and g and b) then
		GF.StopDispelGlow(st.barGroup or self, nil, st)
		return
	end

	local colorMode = dcfg.glowColorMode or defDispel.glowColorMode or "DISPEL"
	local cr, cg, cb = r, g, b
	if colorMode == "CUSTOM" then
		local col = dcfg.glowColor or defDispel.glowColor or GFH.COLOR_WHITE
		cr, cg, cb = unpackColor(col, GFH.COLOR_WHITE)
	end
	local lines = clampNumber(dcfg.glowLines or defDispel.glowLines or 8, 1, 20, 8)
	local freq = clampNumber(dcfg.glowFrequency or defDispel.glowFrequency or 0.25, -1.5, 1.5, 0.25)
	local thickness = clampNumber(dcfg.glowThickness or defDispel.glowThickness or 3, 1, 10, 3)
	local xoff = clampNumber(dcfg.glowX or defDispel.glowX or 0, -10, 10, 0)
	local yoff = clampNumber(dcfg.glowY or defDispel.glowY or 0, -10, 10, 0)
	local effect = dcfg.glowEffect or defDispel.glowEffect or "PIXEL"
	local glowOverOverlay = (dcfg.glowOverOverlay ~= nil) and (dcfg.glowOverOverlay == true) or (defDispel.glowOverOverlay == true)
	if effect ~= "PIXEL" and effect ~= "SHINE" and effect ~= "BLIZZARD" then effect = "PIXEL" end
	local scale = thickness / 3
	if scale < 0.5 then
		scale = 0.5
	elseif scale > 4 then
		scale = 4
	end

	local target, glowFrameLevel = GF.ResolveDispelGlowTarget(self, st, glowOverOverlay)
	local usingGlow = addon.Glow and addon.Glow.Start and addon.Glow.Stop
	local canPixel = LCG and LCG.PixelGlow_Start
	local canShine = LCG and LCG.AutoCastGlow_Start
	local canButton = LCG and LCG.ButtonGlow_Start
	local appliedEffect = effect
	if appliedEffect == "SHINE" and not canShine then
		appliedEffect = "PIXEL"
	elseif appliedEffect == "BLIZZARD" and not usingGlow and not canButton then
		appliedEffect = "PIXEL"
	end
	if appliedEffect == "PIXEL" and not canPixel then
		GF.StopDispelGlow(target, nil, st)
		return
	end
	if st._dispelGlowActive and (st._dispelGlowEffect ~= appliedEffect or st._dispelGlowTarget ~= target) then GF.StopDispelGlow(target, nil, st) end
	local glowColor = { cr, cg, cb, 1 }
	if usingGlow then
		addon.Glow.Start(target, DISPEL_GLOW_KEY, appliedEffect, {
			color = glowColor,
			count = lines,
			frequency = freq,
			scale = scale,
			thickness = thickness,
			xOffset = xoff,
			yOffset = yoff,
			hostFrameLevelOffset = glowFrameLevel,
			frameLevel = glowFrameLevel,
		})
	elseif appliedEffect == "SHINE" and canShine then
		LCG.AutoCastGlow_Start(target, glowColor, lines, freq, scale, xoff, yoff, DISPEL_GLOW_KEY, glowFrameLevel)
	elseif appliedEffect == "BLIZZARD" and canButton then
		LCG.ButtonGlow_Start(target, glowColor, freq, glowFrameLevel)
	else
		LCG.PixelGlow_Start(target, glowColor, lines, freq, nil, thickness, xoff, yoff, nil, DISPEL_GLOW_KEY, glowFrameLevel)
	end
	st._dispelGlowActive = true
	st._dispelGlowEffect = appliedEffect
	st._dispelGlowTarget = target
end

function GF:UpdateRange(self, inRange)
	local st = getState(self)
	if not st then return end
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local scfg = cfg and cfg.status or {}
	local rcfg = scfg.rangeFade or {}
	local fadeAlpha = rcfg.alpha
	if fadeAlpha == nil then fadeAlpha = 0.55 end
	if rcfg.enabled == false then
		if st.frame and st.frame.SetAlpha then st.frame:SetAlpha(1) end
		return
	end
	if isEditModeActive() and self._eqolPreview then
		local previewIndex = st._previewIndex or self._eqolPreviewIndex or 0
		if previewIndex > 0 then
			local sampleMode = (previewIndex - 1) % 3
			if sampleMode == 2 then
				local offA = rcfg.offlineAlpha
				if offA == nil then offA = fadeAlpha end
				if st.frame and st.frame.SetAlpha then st.frame:SetAlpha(offA) end
			elseif st.frame and st.frame.SetAlphaFromBoolean then
				st.frame:SetAlphaFromBoolean(sampleMode == 0, 1, fadeAlpha)
			end
			return
		end
	end
	if IsInGroup and IsInRaid then
		local inGroup = IsInGroup()
		local inRaid = IsInRaid()
		if not inGroup and not inRaid then
			if st.frame and st.frame.SetAlpha then st.frame:SetAlpha(1) end
			return
		end
	end
	local unit = getUnit(self)
	local connected = unit and UnitIsConnected and GFH.UnsecretBool(UnitIsConnected(unit)) or nil
	if connected == false then
		local offA = rcfg.offlineAlpha
		if offA == nil then offA = fadeAlpha end
		if st.frame and st.frame.SetAlpha then st.frame:SetAlpha(offA) end
		return
	end
	if inRange == nil and unit and UnitInRange then inRange = UnitInRange(unit) end
	if type(inRange) ~= "nil" then
		if st.frame and st.frame.SetAlphaFromBoolean then st.frame:SetAlphaFromBoolean(inRange, 1, fadeAlpha) end
	end
end

function GF:UpdatePrivateAuras(self)
	if not (self and UFHelper and UFHelper.ApplyPrivateAuras) then return end
	local st = getState(self)
	if not st then return end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local def = DEFAULTS[kind] or {}
	if GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "privateAuras") then
		local inEditMode = isEditModeActive()
		if inEditMode and (GF._editModeSampleAuras == false or GF._editModeExiting == true) then
			GF:ClearBlizzardAuraContainer(self)
		else
			GF:UpdateBlizzardAuraContainer(self)
		end
		if GF.IsBlizzardAuraRenderTypeEnabled(cfg, def, "dispels") then
			GF:ClearPrivateAuraDispelContainer(self)
		else
			GF:UpdatePrivateAuraDispelContainer(self)
		end
		return
	end
	local pcfg = (cfg and cfg.privateAuras) or def.privateAuras
	local runtimePrivateCfg = GF.GetScaledPrivateAuraConfig(self, pcfg)
	local privateAuraParent = GF.GetLayoutAnchorFrame(st, st.health or self) or self
	local privateAuraLevelParent = st.statusIconLayer or st.healthTextLayer or privateAuraParent or st.health or st.barGroup or self
	if not st.privateAuras then
		if not (pcfg and pcfg.enabled == true) then
			GF:UpdatePrivateAuraDispelContainer(self)
			return
		end
		st.privateAuras = CreateFrame("Frame", nil, privateAuraParent)
		st.privateAuras:EnableMouse(false)
	end
	if st.privateAuras.GetParent and privateAuraParent and st.privateAuras:GetParent() ~= privateAuraParent then st.privateAuras:SetParent(privateAuraParent) end
	if not (pcfg and pcfg.enabled == true) then
		if UFHelper and UFHelper.RemovePrivateAuras then UFHelper.RemovePrivateAuras(st.privateAuras) end
		if UFHelper and UFHelper.UpdatePrivateAuraSound then UFHelper.UpdatePrivateAuraSound(st.privateAuras, nil, runtimePrivateCfg or pcfg or {}) end
		if st.privateAuras and st.privateAuras.Hide then st.privateAuras:Hide() end
		GF:UpdatePrivateAuraDispelContainer(self)
		return
	end
	local inEditMode = isEditModeActive()
	local showSample = inEditMode == true and GF._editModeSampleAuras ~= false
	if inEditMode and showSample == false then
		if UFHelper and UFHelper.RemovePrivateAuras then UFHelper.RemovePrivateAuras(st.privateAuras) end
		if UFHelper and UFHelper.UpdatePrivateAuraSound then UFHelper.UpdatePrivateAuraSound(st.privateAuras, nil, runtimePrivateCfg or pcfg or {}) end
		if st.privateAuras and st.privateAuras.Hide then st.privateAuras:Hide() end
		GF:UpdatePrivateAuraDispelContainer(self)
		return
	end
	UFHelper.ApplyPrivateAuras(st.privateAuras, self.unit, runtimePrivateCfg or pcfg, privateAuraParent, privateAuraLevelParent, showSample)
	GF:UpdatePrivateAuraDispelContainer(self)
end

function GF:UpdateHealthValue(self, unit, st)
	unit = unit or getUnit(self)
	st = st or getState(self)
	if not (unit and st and st.health) then return end
	if UnitExists and not UnitExists(unit) then
		st.health:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.health, 0, false, true)
		if st.incomingHeal then st.incomingHeal:Hide() end
		if st.absorb then st.absorb:Hide() end
		if st.absorb2 then st.absorb2:Hide() end
		if st.overAbsorbGlow then st.overAbsorbGlow:Hide() end
		if st.healAbsorb then st.healAbsorb:Hide() end
		if st.tempMaxHealthLoss then
			GF.SetStatusBarValue(st.tempMaxHealthLoss, 0, false, true)
			st.tempMaxHealthLoss:Hide()
		end
		GF.ClearDataBarText(st)
		return
	end

	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local hc = cfg and cfg.health or {}
	local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
	local reverseAbsorb = hc.absorbReverseFill
	if reverseAbsorb == nil then reverseAbsorb = defH.absorbReverseFill == true end
	local absorbDontOverflow = hc.absorbDontOverflowHealthBar
	if absorbDontOverflow == nil then absorbDontOverflow = defH.absorbDontOverflowHealthBar == true end
	absorbDontOverflow = absorbDontOverflow == true and reverseAbsorb == true
	local reverseHealAbsorb = hc.healAbsorbReverseFill
	if reverseHealAbsorb == nil then reverseHealAbsorb = defH.healAbsorbReverseFill == true end
	local healAbsorbDontOverflow = hc.healAbsorbDontOverflowHealthBar
	if healAbsorbDontOverflow == nil then healAbsorbDontOverflow = defH.healAbsorbDontOverflowHealthBar ~= false end
	healAbsorbDontOverflow = healAbsorbDontOverflow == true and reverseHealAbsorb ~= true
	local calc = GF.EnsureHealPredictionCalculator(st)
	if calc and Enum then
		if calc.SetDamageAbsorbClampMode and Enum.UnitDamageAbsorbClampMode then
			local modes = Enum.UnitDamageAbsorbClampMode
			local mode = absorbDontOverflow and modes.MissingHealthWithoutIncomingHeals or modes.MaximumHealth
			if mode ~= nil and st._lastDamageAbsorbClampMode ~= mode then
				st._lastDamageAbsorbClampMode = mode
				calc:SetDamageAbsorbClampMode(mode)
			end
		end
		if calc.SetHealAbsorbClampMode and Enum.UnitHealAbsorbClampMode then
			local modes = Enum.UnitHealAbsorbClampMode
			local mode = healAbsorbDontOverflow and modes.CurrentHealth or modes.MaximumHealth
			if mode ~= nil and st._lastHealAbsorbClampMode ~= mode then
				st._lastHealAbsorbClampMode = mode
				calc:SetHealAbsorbClampMode(mode)
			end
		end
	end
	if calc and UnitGetDetailedHealPrediction then UnitGetDetailedHealPrediction(unit, "player", calc) end

	local cur = calc and calc.GetCurrentHealth and calc:GetCurrentHealth() or (UnitHealth and UnitHealth(unit))
	if cur == nil then cur = 0 end
	local maxv = calc and calc.GetMaximumHealth and calc:GetMaximumHealth() or (UnitHealthMax and UnitHealthMax(unit))
	if maxv == nil then maxv = 1 end
	local maxForValue = 1
	if issecretvalue and issecretvalue(maxv) then
		maxForValue = maxv
	elseif maxv and maxv > 0 then
		maxForValue = maxv
	end
	local secretHealth = issecretvalue and (issecretvalue(cur) or issecretvalue(maxv))
	local connected = unit and UnitIsConnected and GFH.UnsecretBool(UnitIsConnected(unit)) or nil
	local isDead = unit and UnitIsDead and GFH.UnsecretBool(UnitIsDead(unit)) or nil
	local isGhost = unit and UnitIsGhost and GFH.UnsecretBool(UnitIsGhost(unit)) or nil
	local deadOrGhost = (isDead == true) or (isGhost == true)
	local suppressAuxHealthBars = (connected == false) or deadOrGhost
	local smoothHealth = (hc.smoothFill ~= nil) and (hc.smoothFill == true) or (defH.smoothFill == true)
	local maxForValueSecret = issecretvalue and issecretvalue(maxForValue)
	if maxForValueSecret then
		st.health:SetMinMaxValues(0, maxForValue)
		st._lastHealthMaxForValue = nil
	elseif st._lastHealthMaxForValue ~= maxForValue then
		st._lastHealthMaxForValue = maxForValue
		st.health:SetMinMaxValues(0, maxForValue)
	end
	if connected == false then
		GF.SetStatusBarValue(st.health, maxForValue, false, true)
	elseif deadOrGhost then
		GF.SetStatusBarValue(st.health, 0, false, true)
	elseif secretHealth then
		GF.SetStatusBarValue(st.health, cur or 0, smoothHealth, true)
	else
		GF.SetStatusBarValue(st.health, cur or 0, smoothHealth)
	end
	if st.tempMaxHealthLoss then
		local tempMaxHealthLossEnabled = hc.tempMaxHealthLossEnabled
		if tempMaxHealthLossEnabled == nil then tempMaxHealthLossEnabled = defH.tempMaxHealthLossEnabled ~= false end
		if tempMaxHealthLossEnabled and not suppressAuxHealthBars then
			st.tempMaxHealthLoss:SetMinMaxValues(0, 1)
			GF.SetStatusBarValue(st.tempMaxHealthLoss, (_G.GetUnitTotalModifiedMaxHealthPercent and _G.GetUnitTotalModifiedMaxHealthPercent(unit)) or 0, smoothHealth)
			st.tempMaxHealthLoss:Show()
		else
			GF.SetStatusBarValue(st.tempMaxHealthLoss, 0, false, true)
			st.tempMaxHealthLoss:Hide()
		end
	end

	local incomingHealEnabled = hc.incomingHealEnabled == true
	local absorbEnabled = hc.absorbEnabled ~= false
	local healAbsorbEnabled = hc.healAbsorbEnabled ~= false
	local curSecret = issecretvalue and issecretvalue(cur)
	local inEditMode = isEditModeActive()
	local sampleIncomingHeal = inEditMode and hc.showSampleIncomingHeal == true
	local sampleAbsorb = inEditMode and hc.showSampleAbsorb == true
	local sampleHealAbsorb = inEditMode and hc.showSampleHealAbsorb == true
	local maxIsSecret = issecretvalue and issecretvalue(maxForValue)
	local sampleMax = maxForValue
	if (sampleIncomingHeal or sampleAbsorb or sampleHealAbsorb) and maxIsSecret then sampleMax = 100 end
	if suppressAuxHealthBars then
		if st.incomingHeal then st.incomingHeal:Hide() end
		if st.absorb then st.absorb:Hide() end
		if st.absorb2 then st.absorb2:Hide() end
		if st.overAbsorbGlow then st.overAbsorbGlow:Hide() end
		if st.healAbsorb then st.healAbsorb:Hide() end
	elseif incomingHealEnabled and st.incomingHeal then
		local incomingHeal = 0
		if calc and calc.GetIncomingHeals then
			incomingHeal = calc:GetIncomingHeals() or 0
		elseif UnitGetIncomingHeals then
			incomingHeal = UnitGetIncomingHeals(unit) or 0
		end
		if incomingHeal == nil then incomingHeal = 0 end
		local incomingHealSecret = issecretvalue and issecretvalue(incomingHeal)
		local incomingHealValue = incomingHeal
		if sampleIncomingHeal then
			local useSample = false
			if incomingHealSecret then
				useSample = true
			else
				incomingHealValue = tonumber(incomingHeal) or 0
				if incomingHealValue <= 0 then useSample = true end
			end
			if useSample then
				incomingHealValue = (sampleMax or 1) * 0.25
				incomingHealSecret = false
			end
		elseif not incomingHealSecret then
			incomingHealValue = tonumber(incomingHeal) or 0
		end
		if not incomingHealSecret and not curSecret then
			local missingHealth = (tonumber(maxForValue) or 0) - (tonumber(cur) or 0)
			if missingHealth < 0 then missingHealth = 0 end
			if incomingHealValue > missingHealth then incomingHealValue = missingHealth end
		end
		st.incomingHeal:SetMinMaxValues(0, maxForValue or 1)
		GF.SetStatusBarValue(st.incomingHeal, incomingHealValue or 0, false, true)
		if incomingHealSecret then
			st.incomingHeal:Show()
		elseif incomingHealValue and incomingHealValue > 0 then
			st.incomingHeal:Show()
		else
			st.incomingHeal:Hide()
		end
		local incomingHealR, incomingHealG, incomingHealB, incomingHealA = unpackColor(hc.incomingHealColor, defH.incomingHealColor or GFH.COLOR_INCOMING_HEAL_DEFAULT)
		if st._lastIncomingHealR ~= incomingHealR or st._lastIncomingHealG ~= incomingHealG or st._lastIncomingHealB ~= incomingHealB or st._lastIncomingHealA ~= incomingHealA then
			st._lastIncomingHealR, st._lastIncomingHealG, st._lastIncomingHealB, st._lastIncomingHealA = incomingHealR, incomingHealG, incomingHealB, incomingHealA
			st.incomingHeal:SetStatusBarColor(incomingHealR, incomingHealG, incomingHealB, incomingHealA)
		end
	elseif st.incomingHeal then
		st.incomingHeal:Hide()
	end
	if not suppressAuxHealthBars and absorbEnabled and st.absorb then
		local abs = 0
		local totalAbs
		if calc and calc.GetTotalDamageAbsorbs then totalAbs = calc:GetTotalDamageAbsorbs() end
		if absorbDontOverflow and calc and calc.GetDamageAbsorbs then
			abs = calc:GetDamageAbsorbs() or 0
		elseif totalAbs ~= nil then
			abs = totalAbs or 0
		elseif UnitGetTotalAbsorbs then
			abs = UnitGetTotalAbsorbs(unit) or 0
			totalAbs = abs
		end
		local absSecret = issecretvalue and issecretvalue(abs)
		local absValue = abs
		local glowAbsorbValue = totalAbs ~= nil and totalAbs or abs
		local glowAbsorbSecret = issecretvalue and issecretvalue(glowAbsorbValue)
		if sampleAbsorb then
			local useSample = false
			if absSecret then
				useSample = true
			else
				absValue = tonumber(abs) or 0
				if absValue <= 0 then useSample = true end
			end
			if useSample then
				absValue = (sampleMax or 1) * 0.6
				glowAbsorbValue = absValue
				absSecret = false
				glowAbsorbSecret = false
			end
		else
			if not absSecret then absValue = tonumber(abs) or 0 end
			if not glowAbsorbSecret then glowAbsorbValue = tonumber(glowAbsorbValue) or 0 end
		end
		if absorbDontOverflow and not absSecret and not curSecret then
			local missingHealth = (tonumber(maxForValue) or 0) - (tonumber(cur) or 0)
			if missingHealth < 0 then missingHealth = 0 end
			if (absValue or 0) > missingHealth then absValue = missingHealth end
		end
		local absorbMax = (sampleAbsorb and sampleMax) or maxForValue or 1
		st.absorb:SetMinMaxValues(0, absorbMax)
		GF.SetStatusBarValue(st.absorb, absValue or 0, false, true)
		if reverseAbsorb and st.absorb2 then
			local _, maxHealth = st.health:GetMinMaxValues()
			if maxHealth == nil then maxHealth = absorbMax end
			st.absorb2:SetMinMaxValues(0, maxHealth or 1)
			GF.SetStatusBarValue(st.absorb2, absValue or 0, false, true)
		end
		if reverseAbsorb and st.absorb2 then
			st.absorb2:SetAlpha(1)
			st.absorb2:Show()
			if st.absorb then
				if absorbDontOverflow then
					st.absorb:SetAlpha(0)
					st.absorb:Hide()
				else
					st.absorb:SetAlpha(1)
					st.absorb:Show()
				end
			end
		elseif st.absorb then
			st.absorb:SetAlpha(1)
			st.absorb:Show()
			if st.absorb2 then
				st.absorb2:SetAlpha(0)
				st.absorb2:Show()
			end
		end
		local ar, ag, ab, aa
		if UFHelper and UFHelper.getAbsorbColor then
			ar, ag, ab, aa = UFHelper.getAbsorbColor(hc, defH)
		else
			ar, ag, ab, aa = 0.85, 0.95, 1, 0.7
		end
		if st._lastAbsorbR ~= ar or st._lastAbsorbG ~= ag or st._lastAbsorbB ~= ab or st._lastAbsorbA ~= aa then
			st._lastAbsorbR, st._lastAbsorbG, st._lastAbsorbB, st._lastAbsorbA = ar, ag, ab, aa
			st.absorb:SetStatusBarColor(ar or 0.85, ag or 0.95, ab or 1, aa or 0.7)
		end
		if reverseAbsorb and st.absorb2 then st.absorb2:SetStatusBarColor(ar or 0.85, ag or 0.95, ab or 1, aa or 0.7) end
		if st.overAbsorbGlow then
			local useAbsorbGlow = hc.useAbsorbGlow
			if useAbsorbGlow == nil then useAbsorbGlow = defH.useAbsorbGlow == true end
			if useAbsorbGlow then
				st.overAbsorbGlow:SetAlpha(glowAbsorbValue or 0)
				st.overAbsorbGlow:Show()
			else
				st.overAbsorbGlow:SetAlpha(0)
				st.overAbsorbGlow:Hide()
			end
		end
	elseif st.absorb then
		st.absorb:Hide()
		if st.absorb2 then st.absorb2:Hide() end
		if st.overAbsorbGlow then st.overAbsorbGlow:Hide() end
	end

	if not suppressAuxHealthBars and healAbsorbEnabled and st.healAbsorb then
		local healAbs = 0
		if calc and calc.GetHealAbsorbs then
			healAbs = calc:GetHealAbsorbs() or 0
		elseif UnitGetTotalHealAbsorbs then
			healAbs = UnitGetTotalHealAbsorbs(unit) or 0
		end
		local healSecret = issecretvalue and issecretvalue(healAbs)
		local healValue = healAbs
		if sampleHealAbsorb then
			local useSample = false
			if healSecret then
				useSample = true
			else
				healValue = tonumber(healAbs) or 0
				if healValue <= 0 then useSample = true end
			end
			if useSample then
				healValue = (sampleMax or 1) * 0.35
				healSecret = false
			end
		else
			if not healSecret then healValue = tonumber(healAbs) or 0 end
		end
		st.healAbsorb:SetMinMaxValues(0, (sampleHealAbsorb and sampleMax) or maxForValue or 1)
		if healAbsorbDontOverflow and not healSecret and not curSecret then
			if (cur or 0) < (healValue or 0) then healValue = cur or 0 end
		end
		GF.SetStatusBarValue(st.healAbsorb, healValue or 0, false, true)
		if healSecret then
			st.healAbsorb:Show()
		elseif healValue and healValue > 0 then
			st.healAbsorb:Show()
		else
			st.healAbsorb:Hide()
		end
		local har, hag, hab, haa
		if UFHelper and UFHelper.getHealAbsorbColor then
			har, hag, hab, haa = UFHelper.getHealAbsorbColor(hc, defH)
		else
			har, hag, hab, haa = 1, 0.3, 0.3, 0.7
		end
		if st._lastHealAbsorbR ~= har or st._lastHealAbsorbG ~= hag or st._lastHealAbsorbB ~= hab or st._lastHealAbsorbA ~= haa then
			st._lastHealAbsorbR, st._lastHealAbsorbG, st._lastHealAbsorbB, st._lastHealAbsorbA = har, hag, hab, haa
			st.healAbsorb:SetStatusBarColor(har or 1, hag or 0.3, hab or 0.3, haa or 0.7)
		end
	elseif st.healAbsorb then
		st.healAbsorb:Hide()
	end

	local leftMode = (hc.textLeft ~= nil) and hc.textLeft or defH.textLeft or "NONE"
	local centerMode = (hc.textCenter ~= nil) and hc.textCenter or defH.textCenter or "NONE"
	local rightMode = (hc.textRight ~= nil) and hc.textRight or defH.textRight or "NONE"
	local hasText = (leftMode ~= "NONE") or (centerMode ~= "NONE") or (rightMode ~= "NONE")
	local dbc = cfg and cfg.dataBar or {}
	local defDB = (DEFAULTS[kind] and DEFAULTS[kind].dataBar) or {}
	local dbLeft = dbc.textLeft or defDB.textLeft or "NONE"
	local dbCenter = dbc.textCenter or defDB.textCenter or "NONE"
	local dbRight = dbc.textRight or defDB.textRight or "NONE"
	local dataBarHasText = dbc.enabled == true and ((dbLeft ~= "NONE") or (dbCenter ~= "NONE") or (dbRight ~= "NONE"))
	local scfg = cfg and cfg.status or {}
	local us = scfg.unitStatus or {}
	local hideTextOffline = us.hideHealthTextWhenOffline == true
	local connected = unit and UnitIsConnected and GFH.UnsecretBool(UnitIsConnected(unit)) or nil
	local isDead = unit and UnitIsDeadOrGhost and GFH.UnsecretBool(UnitIsDeadOrGhost(unit)) or nil
	if hideTextOffline and connected == false then
		if st.healthTextLeft then st.healthTextLeft:SetText("") end
		if st.healthTextCenter then st.healthTextCenter:SetText("") end
		if st.healthTextRight then st.healthTextRight:SetText("") end
		st._lastHealthTextLeft, st._lastHealthTextCenter, st._lastHealthTextRight = nil, nil, nil
		st._nextHealthTextUpdateAt = nil
		GF.ClearDataBarText(st)
		return
	end
	if isDead == true then
		if st.healthTextLeft then st.healthTextLeft:SetText("") end
		if st.healthTextCenter then st.healthTextCenter:SetText("") end
		if st.healthTextRight then st.healthTextRight:SetText("") end
		st._lastHealthTextLeft, st._lastHealthTextCenter, st._lastHealthTextRight = nil, nil, nil
		st._nextHealthTextUpdateAt = nil
		leftMode, centerMode, rightMode = "NONE", "NONE", "NONE"
		hasText = false
	end
	if (hasText or dataBarHasText) and (st.healthTextLeft or st.healthTextCenter or st.healthTextRight or st.dataBarTextLeft or st.dataBarTextCenter or st.dataBarTextRight) then
		local allowSecretText = secretHealth and addon.variables and addon.variables.isMidnight
		if secretHealth and not allowSecretText then
			if st.healthTextLeft then st.healthTextLeft:SetText("") end
			if st.healthTextCenter then st.healthTextCenter:SetText("") end
			if st.healthTextRight then st.healthTextRight:SetText("") end
			st._lastHealthTextLeft, st._lastHealthTextCenter, st._lastHealthTextRight = nil, nil, nil
			st._nextHealthTextUpdateAt = nil
			GF.ClearDataBarText(st)
		else
			local allowTextRefresh = true
			if secretHealth then
				local now = GetTime and GetTime() or 0
				local nextAt = st._nextHealthTextUpdateAt or 0
				if now < nextAt then
					allowTextRefresh = false
				else
					st._nextHealthTextUpdateAt = now + 0.1
				end
			else
				st._nextHealthTextUpdateAt = nil
			end
			if allowTextRefresh then
				local delimiter = (UFHelper and UFHelper.getTextDelimiter and UFHelper.getTextDelimiter(hc, defH)) or (hc.textDelimiter or defH.textDelimiter or " ")
				local delimiter2 = (UFHelper and UFHelper.getTextDelimiterSecondary and UFHelper.getTextDelimiterSecondary(hc, defH, delimiter))
					or (hc.textDelimiterSecondary or defH.textDelimiterSecondary or delimiter)
				local delimiter3 = (UFHelper and UFHelper.getTextDelimiterTertiary and UFHelper.getTextDelimiterTertiary(hc, defH, delimiter, delimiter2))
					or (hc.textDelimiterTertiary or defH.textDelimiterTertiary or delimiter2)
				local useShort = hc.useShortNumbers ~= false
				local roundPercent = hc.roundPercent == true
				local hidePercentSymbol = hc.hidePercentSymbol == true
				local percentVal
				if UFHelper and (UFHelper.textModeUsesPercent(leftMode) or UFHelper.textModeUsesPercent(centerMode) or UFHelper.textModeUsesPercent(rightMode)) then
					percentVal = getHealthPercent(unit, cur, maxv, calc)
				end
				local levelText
				if UFHelper and UFHelper.textModeUsesLevel then
					if UFHelper.textModeUsesLevel(leftMode) or UFHelper.textModeUsesLevel(centerMode) or UFHelper.textModeUsesLevel(rightMode) then levelText = getSafeLevelText(unit, false) end
				end
				local missingValue
				if GFH.TextModeUsesDeficit(leftMode) or GFH.TextModeUsesDeficit(centerMode) or GFH.TextModeUsesDeficit(rightMode) then
					if UnitHealthMissing then missingValue = UnitHealthMissing(unit) end
					if not (issecretvalue and issecretvalue(missingValue)) then
						if missingValue == nil and not secretHealth then
							if type(cur) == "number" and type(maxv) == "number" then missingValue = maxv - cur end
						end
					end
				end
				setTextSlot(st, st.healthTextLeft, "_lastHealthTextLeft", leftMode, cur, maxv, useShort, percentVal, delimiter, delimiter2, delimiter3, hidePercentSymbol, levelText, missingValue, roundPercent)
				setTextSlot(
					st,
					st.healthTextCenter,
					"_lastHealthTextCenter",
					centerMode,
					cur,
					maxv,
					useShort,
					percentVal,
					delimiter,
					delimiter2,
					delimiter3,
					hidePercentSymbol,
					levelText,
					missingValue,
					roundPercent
				)
				setTextSlot(st, st.healthTextRight, "_lastHealthTextRight", rightMode, cur, maxv, useShort, percentVal, delimiter, delimiter2, delimiter3, hidePercentSymbol, levelText, missingValue, roundPercent)
				if dbc.enabled == true and st.dataBar and st.dataBar:IsShown() then
					local dbDelimiter = (UFHelper and UFHelper.getTextDelimiter and UFHelper.getTextDelimiter(dbc, defDB)) or (dbc.textDelimiter or defDB.textDelimiter or " ")
					local dbDelimiter2 = (UFHelper and UFHelper.getTextDelimiterSecondary and UFHelper.getTextDelimiterSecondary(dbc, defDB, dbDelimiter))
						or (dbc.textDelimiterSecondary or defDB.textDelimiterSecondary or dbDelimiter)
					local dbDelimiter3 = (UFHelper and UFHelper.getTextDelimiterTertiary and UFHelper.getTextDelimiterTertiary(dbc, defDB, dbDelimiter, dbDelimiter2))
						or (dbc.textDelimiterTertiary or defDB.textDelimiterTertiary or dbDelimiter2)
					local dbUseShort = dbc.useShortNumbers ~= false
					local dbHidePercentSymbol = dbc.hidePercentSymbol == true
					local dbPercentVal = percentVal
					if UFHelper and dbPercentVal == nil and (UFHelper.textModeUsesPercent(dbLeft) or UFHelper.textModeUsesPercent(dbCenter) or UFHelper.textModeUsesPercent(dbRight)) then
						dbPercentVal = getHealthPercent(unit, cur, maxv, calc)
					end
					local dbLevelText = levelText
					if UFHelper and UFHelper.textModeUsesLevel and dbLevelText == nil then
						if UFHelper.textModeUsesLevel(dbLeft) or UFHelper.textModeUsesLevel(dbCenter) or UFHelper.textModeUsesLevel(dbRight) then dbLevelText = getSafeLevelText(unit, false) end
					end
					local dbNameText
					if dbLeft == "NAME" or dbCenter == "NAME" or dbRight == "NAME" then
						dbNameText = (UnitName and UnitName(unit)) or ""
						if isEditModeActive() and self._eqolPreview and st._previewName then dbNameText = st._previewName end
					end
					local dbMissingValue = missingValue
					if dbMissingValue == nil and (GFH.TextModeUsesDeficit(dbLeft) or GFH.TextModeUsesDeficit(dbCenter) or GFH.TextModeUsesDeficit(dbRight)) then
						if UnitHealthMissing then dbMissingValue = UnitHealthMissing(unit) end
						if dbMissingValue == nil and not secretHealth and type(cur) == "number" and type(maxv) == "number" then dbMissingValue = maxv - cur end
					end
					local dbRoundPercent = dbc.roundPercent == true
					setTextSlot(st, st.dataBarTextLeft, "_lastDataBarTextLeft", dbLeft, cur, maxv, dbUseShort, dbPercentVal, dbDelimiter, dbDelimiter2, dbDelimiter3, dbHidePercentSymbol, dbLevelText, dbMissingValue, dbRoundPercent, dbNameText)
					setTextSlot(st, st.dataBarTextCenter, "_lastDataBarTextCenter", dbCenter, cur, maxv, dbUseShort, dbPercentVal, dbDelimiter, dbDelimiter2, dbDelimiter3, dbHidePercentSymbol, dbLevelText, dbMissingValue, dbRoundPercent, dbNameText)
					setTextSlot(st, st.dataBarTextRight, "_lastDataBarTextRight", dbRight, cur, maxv, dbUseShort, dbPercentVal, dbDelimiter, dbDelimiter2, dbDelimiter3, dbHidePercentSymbol, dbLevelText, dbMissingValue, dbRoundPercent, dbNameText)
				else
					GF.ClearDataBarText(st)
				end
			end
		end
	elseif st.healthTextLeft or st.healthTextCenter or st.healthTextRight then
		if st.healthTextLeft then st.healthTextLeft:SetText("") end
		if st.healthTextCenter then st.healthTextCenter:SetText("") end
		if st.healthTextRight then st.healthTextRight:SetText("") end
		st._lastHealthTextLeft, st._lastHealthTextCenter, st._lastHealthTextRight = nil, nil, nil
		st._nextHealthTextUpdateAt = nil
		GF.ClearDataBarText(st)
	end
end

function GF:UpdateHealthStyle(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st and st.health) then return end
	if UnitExists and not UnitExists(unit) then return end

	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local hc = cfg and cfg.health or {}
	local kind = self._eqolGroupKind or "party"
	local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}

	local healthTexKey = getEffectiveBarTexture(cfg, hc)
	if st.health.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
		if st._lastHealthTexture ~= healthTexKey then
			if Pixel and Pixel.SetStatusBarTexture then
				Pixel.SetStatusBarTexture(st.health, UFHelper.resolveTexture(healthTexKey))
			else
				st.health:SetStatusBarTexture(UFHelper.resolveTexture(healthTexKey))
			end
			if UFHelper.configureSpecialTexture then UFHelper.configureSpecialTexture(st.health, "HEALTH", healthTexKey, hc) end
			st._lastHealthTexture = healthTexKey
			stabilizeStatusBarTexture(st.health)
		end
	end

	if st.health and st.health.SetStatusBarDesaturated then
		if st._lastHealthDesat ~= true then
			st._lastHealthDesat = true
			st.health:SetStatusBarDesaturated(true)
		end
	end

	local r, g, b, a
	local useCustom = hc.useCustomColor == true
	if useCustom then
		r, g, b, a = unpackColor(hc.color, defH.color or GFH.COLOR_HEALTH_DEFAULT)
	elseif hc.useClassColor == true then
		if not st._classR then GF:EnsureUnitClassColor(self, st, unit) end
		if st._classR then
			r, g, b, a = st._classR, st._classG, st._classB, st._classA or 1
		else
			r, g, b, a = unpackColor(hc.color, defH.color or GFH.COLOR_HEALTH_DEFAULT)
		end
	else
		r, g, b, a = unpackColor(hc.color, defH.color or GFH.COLOR_HEALTH_DEFAULT)
	end

	local connected = UnitIsConnected and GFH.UnsecretBool(UnitIsConnected(unit))
	if connected == false then
		r, g, b, a = 0.5, 0.5, 0.5, 1
	end

	local renderR, renderG, renderB, renderA = r, g, b, a
	if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ApplyHealthTint then
		renderR, renderG, renderB, renderA = UF.GroupFramesHealerBuffs.ApplyHealthTint(st, renderR, renderG, renderB, renderA)
	end

	if st._lastHealthR ~= r or st._lastHealthG ~= g or st._lastHealthB ~= b or st._lastHealthA ~= a then
		st._lastHealthR, st._lastHealthG, st._lastHealthB, st._lastHealthA = r, g, b, a
	end
	if st._lastHealthRenderR ~= renderR or st._lastHealthRenderG ~= renderG or st._lastHealthRenderB ~= renderB or st._lastHealthRenderA ~= renderA then
		st._lastHealthRenderR, st._lastHealthRenderG, st._lastHealthRenderB, st._lastHealthRenderA = renderR, renderG, renderB, renderA
		st.health:SetStatusBarColor(renderR, renderG, renderB, renderA or 1)
	end
end

function GF:UpdateHealth(self)
	GF:UpdateHealthStyle(self)
	GF:UpdateHealthValue(self)
end

function GF:UpdatePowerVisibility(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st and st.power) then return false end
	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local pcfg = cfg and cfg.power or {}
	if st._wantsPower == false then
		if st.powerTextLeft then st.powerTextLeft:SetText("") end
		if st.powerTextCenter then st.powerTextCenter:SetText("") end
		if st.powerTextRight then st.powerTextRight:SetText("") end
		if st.power:IsShown() then st.power:Hide() end
		if not st._powerHidden then
			st._powerHidden = true
			GF:LayoutButton(self)
		end
		return false
	end
	local showPower = shouldShowPowerForUnit(pcfg, unit, st)
	if not showPower then
		if st.powerTextLeft then st.powerTextLeft:SetText("") end
		if st.powerTextCenter then st.powerTextCenter:SetText("") end
		if st.powerTextRight then st.powerTextRight:SetText("") end
		if st.power:IsShown() then st.power:Hide() end
		if not st._powerHidden then
			st._powerHidden = true
			GF:LayoutButton(self)
		end
		return false
	end
	if st._powerHidden then
		st._powerHidden = nil
		GF:LayoutButton(self)
	end
	if not st.power:IsShown() then st.power:Show() end
	return true
end

function GF:UpdatePowerValue(self, unit, st)
	unit = unit or getUnit(self)
	st = st or getState(self)
	if not (unit and st and st.power) then return end
	if st._wantsPower == false or st._powerHidden then return end
	if UnitExists and not UnitExists(unit) then
		st.power:SetMinMaxValues(0, 1)
		GF.SetStatusBarValue(st.power, 0, false, true)
		return
	end
	local powerType = st._powerType
	if powerType == nil and UnitPowerType then
		powerType, st._powerToken = UnitPowerType(unit)
		st._powerType = powerType
	end
	local cur = UnitPower and UnitPower(unit, powerType)
	if cur == nil then cur = 0 end
	local maxv = UnitPowerMax and UnitPowerMax(unit, powerType)
	if maxv == nil then maxv = 1 end
	local maxForValue = 1
	if issecretvalue and issecretvalue(maxv) then
		maxForValue = maxv
	elseif maxv and maxv > 0 then
		maxForValue = maxv
	end
	local secretPower = issecretvalue and (issecretvalue(cur) or issecretvalue(maxv))
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local kind = self._eqolGroupKind or "party"
	local pcfg = cfg and cfg.power or {}
	local defP = (DEFAULTS[kind] and DEFAULTS[kind].power) or {}
	local smoothPower = (pcfg.smoothFill ~= nil) and (pcfg.smoothFill == true) or (defP.smoothFill == true)
	if secretPower then
		st.power:SetMinMaxValues(0, maxForValue)
		GF.SetStatusBarValue(st.power, cur or 0, smoothPower, true)
	else
		if st._lastPowerMax ~= maxForValue then
			st.power:SetMinMaxValues(0, maxForValue)
			st._lastPowerMax = maxForValue
		end
		GF.SetStatusBarValue(st.power, cur, smoothPower)
		st._lastPowerCur = cur
	end

	local leftMode = (pcfg.textLeft ~= nil) and pcfg.textLeft or defP.textLeft or "NONE"
	local centerMode = (pcfg.textCenter ~= nil) and pcfg.textCenter or defP.textCenter or "NONE"
	local rightMode = (pcfg.textRight ~= nil) and pcfg.textRight or defP.textRight or "NONE"
	local hasText = (leftMode ~= "NONE") or (centerMode ~= "NONE") or (rightMode ~= "NONE")
	if hasText and (st.powerTextLeft or st.powerTextCenter or st.powerTextRight) then
		local allowSecretText = secretPower and addon.variables and addon.variables.isMidnight
		if secretPower and not allowSecretText then
			if st.powerTextLeft then st.powerTextLeft:SetText("") end
			if st.powerTextCenter then st.powerTextCenter:SetText("") end
			if st.powerTextRight then st.powerTextRight:SetText("") end
			st._lastPowerTextLeft, st._lastPowerTextCenter, st._lastPowerTextRight = nil, nil, nil
		else
			local maxZero = (not issecretvalue or not issecretvalue(maxv)) and (maxv == 0)
			if maxZero then
				if st.powerTextLeft then st.powerTextLeft:SetText("") end
				if st.powerTextCenter then st.powerTextCenter:SetText("") end
				if st.powerTextRight then st.powerTextRight:SetText("") end
				st._lastPowerTextLeft, st._lastPowerTextCenter, st._lastPowerTextRight = nil, nil, nil
			else
				local delimiter = (UFHelper and UFHelper.getTextDelimiter and UFHelper.getTextDelimiter(pcfg, defP)) or (pcfg.textDelimiter or defP.textDelimiter or " ")
				local delimiter2 = (UFHelper and UFHelper.getTextDelimiterSecondary and UFHelper.getTextDelimiterSecondary(pcfg, defP, delimiter))
					or (pcfg.textDelimiterSecondary or defP.textDelimiterSecondary or delimiter)
				local delimiter3 = (UFHelper and UFHelper.getTextDelimiterTertiary and UFHelper.getTextDelimiterTertiary(pcfg, defP, delimiter, delimiter2))
					or (pcfg.textDelimiterTertiary or defP.textDelimiterTertiary or delimiter2)
				local useShort = pcfg.useShortNumbers ~= false
				local roundPercent = pcfg.roundPercent == true
				local hidePercentSymbol = pcfg.hidePercentSymbol == true
				local percentVal
				if UFHelper and (UFHelper.textModeUsesPercent(leftMode) or UFHelper.textModeUsesPercent(centerMode) or UFHelper.textModeUsesPercent(rightMode)) then
					if addon.variables and addon.variables.isMidnight then
						percentVal = getPowerPercent(unit, powerType or 0, cur, maxv)
					elseif not secretPower then
						percentVal = getPowerPercent(unit, powerType or 0, cur, maxv)
					end
				end
				local levelText
				if UFHelper and UFHelper.textModeUsesLevel then
					if UFHelper.textModeUsesLevel(leftMode) or UFHelper.textModeUsesLevel(centerMode) or UFHelper.textModeUsesLevel(rightMode) then levelText = getSafeLevelText(unit, false) end
				end
				setTextSlot(st, st.powerTextLeft, "_lastPowerTextLeft", leftMode, cur, maxv, useShort, percentVal, delimiter, delimiter2, delimiter3, hidePercentSymbol, levelText, nil, roundPercent)
				setTextSlot(st, st.powerTextCenter, "_lastPowerTextCenter", centerMode, cur, maxv, useShort, percentVal, delimiter, delimiter2, delimiter3, hidePercentSymbol, levelText, nil, roundPercent)
				setTextSlot(st, st.powerTextRight, "_lastPowerTextRight", rightMode, cur, maxv, useShort, percentVal, delimiter, delimiter2, delimiter3, hidePercentSymbol, levelText, nil, roundPercent)
			end
		end
	elseif st.powerTextLeft or st.powerTextCenter or st.powerTextRight then
		if st.powerTextLeft then st.powerTextLeft:SetText("") end
		if st.powerTextCenter then st.powerTextCenter:SetText("") end
		if st.powerTextRight then st.powerTextRight:SetText("") end
		st._lastPowerTextLeft, st._lastPowerTextCenter, st._lastPowerTextRight = nil, nil, nil
	end
end

function GF:UpdatePowerStyle(self)
	local unit = getUnit(self)
	local st = getState(self)
	if not (unit and st and st.power) then return end
	if st._wantsPower == false or st._powerHidden then return end
	if not UnitPowerType then return end
	if st.power and st.power.SetStatusBarDesaturated then
		if st._lastPowerDesat ~= true then
			st._lastPowerDesat = true
			st.power:SetStatusBarDesaturated(true)
		end
	end
	local powerType, powerToken = UnitPowerType(unit)
	st._powerType, st._powerToken = powerType, powerToken

	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	local pcfg = cfg and cfg.power or {}
	local powerTexKey = getEffectiveBarTexture(cfg, pcfg)
	local powerKey = powerToken or powerType or "MANA"
	local texChanged = st._lastPowerTexture ~= powerTexKey
	if st.power.SetStatusBarTexture and UFHelper and UFHelper.resolveTexture then
		if texChanged then
			if Pixel and Pixel.SetStatusBarTexture then
				Pixel.SetStatusBarTexture(st.power, UFHelper.resolveTexture(powerTexKey))
			else
				st.power:SetStatusBarTexture(UFHelper.resolveTexture(powerTexKey))
			end
			stabilizeStatusBarTexture(st.power)
		end
	end

	if UFHelper and UFHelper.configureSpecialTexture then
		local needsAtlas = (powerTexKey == nil or powerTexKey == "" or powerTexKey == "DEFAULT")
		if st._lastPowerToken ~= powerKey or texChanged or needsAtlas then UFHelper.configureSpecialTexture(st.power, powerKey, powerTexKey, pcfg, powerType) end
	end
	st._lastPowerToken = powerKey
	st._lastPowerTexture = powerTexKey
	stabilizeStatusBarTexture(st.power)
	if st.power.SetStatusBarDesaturated and UFHelper and UFHelper.isPowerDesaturated then
		local desat = UFHelper.isPowerDesaturated(powerType, powerToken)
		if st._lastPowerDesat ~= desat then
			st._lastPowerDesat = desat
			st.power:SetStatusBarDesaturated(desat)
		end
	end
	local pr, pg, pb, pa
	if UFHelper and UFHelper.getPowerColor then
		pr, pg, pb, pa = UFHelper.getPowerColor(powerType, powerToken)
	else
		local c = PowerBarColor and (PowerBarColor[powerKey] or PowerBarColor[powerType] or PowerBarColor["MANA"])
		if c then
			pr, pg, pb, pa = c.r or c[1] or 0, c.g or c[2] or 0, c.b or c[3] or 1, c.a or c[4] or 1
		end
	end
	if not pr then
		pr, pg, pb, pa = 0, 0.5, 1, 1
	end
	local alpha = pa or 1
	if st._lastPowerR ~= pr or st._lastPowerG ~= pg or st._lastPowerB ~= pb or st._lastPowerA ~= alpha then
		st._lastPowerR, st._lastPowerG, st._lastPowerB, st._lastPowerA = pr, pg, pb, alpha
		st.power:SetStatusBarColor(pr, pg, pb, pa or 1)
	end
end

function GF:UpdatePower(self)
	if not GF:UpdatePowerVisibility(self) then return end
	GF:UpdatePowerStyle(self)
	GF:UpdatePowerValue(self)
end

function GF:UpdatePortrait(self, unit, st)
	if not self then return end
	unit = unit or getUnit(self)
	st = st or getState(self)
	if not (st and st.portrait) then return end

	local kind = self._eqolGroupKind or "party"
	local cfg = self._eqolCfg or getCfg(kind)
	local portraitEnabled, _, portraitSquareBackground, _, portraitMode = GF.ResolveGroupPortraitConfig(cfg, kind)
	if st._portraitEnabled ~= nil then portraitEnabled = st._portraitEnabled == true end
	if st._portraitSquareBackground ~= nil then portraitSquareBackground = st._portraitSquareBackground == true end

	if not portraitEnabled or not unit then
		st.portrait:SetTexture(nil)
		st.portrait:Hide()
		if st.portraitBg then st.portraitBg:Hide() end
		if st.portraitHolder then st.portraitHolder:Hide() end
		GF.ApplyGroupPortraitSeparator(cfg, kind, st, false)
		return
	end

	if UnitExists and not UnitExists(unit) then
		st.portrait:SetTexture(nil)
		st.portrait:Hide()
		if st.portraitBg then st.portraitBg:Hide() end
		if st.portraitHolder then st.portraitHolder:Hide() end
		GF.ApplyGroupPortraitSeparator(cfg, kind, st, false)
		return
	end

	if UF.ApplyPortraitTexture then
		UF.ApplyPortraitTexture(st.portrait, unit, portraitMode)
	else
		st.portrait:SetTexCoord(0.08, 0.92, 0.08, 0.92)
		SetPortraitTexture(st.portrait, unit)
	end
	st.portrait:Show()
	if st.portraitHolder then st.portraitHolder:Show() end
	if st.portraitBg then
		if portraitSquareBackground then
			st.portraitBg:Show()
		else
			st.portraitBg:Hide()
		end
	end
	GF.ApplyGroupPortraitSeparator(cfg, kind, st, true)
end

function GF:UpdateAll(self)
	GF:UpdateName(self)
	GF:UpdateStatusText(self)
	GF:UpdateLevel(self)
	GF:UpdateHealth(self)
	GF:UpdatePower(self)
	GF:UpdatePortrait(self)
	GF:UpdateRoleIcon(self)
	GF:UpdateRaidIcon(self)
	GF:UpdateGroupIcons(self)
	GF:UpdateStatusIcons(self)
	GF:UpdateAuras(self)
	GF:UpdateRange(self)
	GF:UpdateHighlightState(self)
	local unit = getUnit(self)
	if unit then GF.RememberUnitConnectedState(getState(self), unit) end
end

GF._dropdown = GF._dropdown or nil

local function ensureDropDown()
	if GF._dropdown and GF._dropdown.GetName then return GF._dropdown end
	GF._dropdown = CreateFrame("Frame", "EQOLUFGroupDropDown", UIParent, "UIDropDownMenuTemplate")
	return GF._dropdown
end

local function resolveMenuType(unit)
	if unit == "player" then return "SELF" end
	if UnitIsUnit and UnitIsUnit(unit, "player") then return "SELF" end
	if UnitInRaid and UnitInRaid(unit) then return "RAID_PLAYER" end
	if UnitInParty and UnitInParty(unit) then return "PARTY" end
	return "RAID_PLAYER"
end

function GF:OpenUnitMenu(self)
	local unit = getUnit(self)
	if not unit then return end

	if UnitPopup_OpenMenu then
		pcall(function() UnitPopup_OpenMenu(resolveMenuType(unit), { unit = unit }) end)
		return
	end

	if UnitPopup_ShowMenu then
		local dd = ensureDropDown()
		local which = resolveMenuType(unit)
		local name = (UnitName and UnitName(unit))
		UnitPopup_ShowMenu(dd, which, unit, name)
	end
end

function GF.UnitButton_OnLoad(self)
	local parent = self and self.GetParent and self:GetParent()
	if parent and parent._eqolKind then
		self._eqolGroupKind = parent._eqolKind
		self._eqolUseSecureUnitAttribute = true
	end

	GF:BuildButton(self)

	local unit = getUnit(self)
	if unit then
		GF:UnitButton_SetUnit(self, unit)
	else
		GF:UpdateAll(self)
	end
end

function GF:UnitButton_SetUnit(self, unit)
	if not self then return end
	self.unit = unit
	local st = self._eqolUFState
	if st then
		st._auraCache = nil
		st._auraCacheByKey = nil
		st._auraQueryMax = nil
		clearDispelAuraState(st)
	end
	GF:CacheUnitStatic(self)
	if self._eqolUFState then GF:LayoutButton(self) end

	GF:UnitButton_RegisterUnitEvents(self, unit)
	GF:UpdatePrivateAuras(self)

	GF:UpdateAll(self)
end

function GF:UnitButton_UnregisterUnitEvents(self)
	if not (self and self._eqolRegEv) then return end
	for ev in pairs(self._eqolRegEv) do
		if self.UnregisterEvent then self:UnregisterEvent(ev) end
		self._eqolRegEv[ev] = nil
	end
end

function GF:UnitButton_ClearUnit(self)
	if not self then return end
	self.unit = nil
	GF:UnitButton_UnregisterUnitEvents(self)
	local st = self._eqolUFState
	if st then
		GFH.CancelReadyCheckIconTimer(st)
		hideDispelTint(st)
		GF.StopDispelGlow(st.barGroup or self, nil, st)
		st._guid = nil
		st._unitToken = nil
		st._class = nil
		st._powerType = nil
		st._powerToken = nil
		st._classR, st._classG, st._classB, st._classA = nil, nil, nil, nil
		if st._healPredictionCalc and st._healPredictionCalc.ResetPredictedValues then st._healPredictionCalc:ResetPredictedValues() end
		st._auraCache = nil
		st._auraCacheByKey = nil
		st._auraQueryMax = nil
		st._lastSummonAtlas = nil
		st._summonActiveReal = false
		st._phaseReason = nil
		clearDispelAuraState(st)
		if st.incomingHeal then st.incomingHeal:Hide() end
		if st.portrait then
			st.portrait:SetTexture(nil)
			st.portrait:Hide()
		end
		if st.portraitBg then st.portraitBg:Hide() end
		if st.portraitHolder then st.portraitHolder:Hide() end
		if st.portraitSeparator then st.portraitSeparator:Hide() end
	end
	if GF.ClearAuraSamplesOnButton then GF.ClearAuraSamplesOnButton(self) end
	if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then UF.GroupFramesHealerBuffs.ClearButton(self) end
	if st then st._healerBuffPlacementActive = nil end
	if st and st.privateAuras and UFHelper then
		if UFHelper.RemovePrivateAuras then UFHelper.RemovePrivateAuras(st.privateAuras) end
		if UFHelper.UpdatePrivateAuraSound then UFHelper.UpdatePrivateAuraSound(st.privateAuras, nil, (self._eqolCfg and self._eqolCfg.privateAuras) or {}) end
	end
	if st and st.blizzardAuras and UFHelper and UFHelper.RemovePrivateAuras then
		UFHelper.RemovePrivateAuras(st.blizzardAuras)
		if st.blizzardAuras.Hide then st.blizzardAuras:Hide() end
	end
	GF:ClearPrivateAuraDispelContainer(self)
end

function GF:UnitButton_RegisterUnitEvents(self, unit)
	if not (self and unit) then return end
	local cfg = self._eqolCfg or getCfg(self._eqolGroupKind or "party")
	updateButtonConfig(self, cfg)

	self._eqolRegEv = self._eqolRegEv or {}
	local registered = self._eqolRegEv
	local desired = self._eqolRegDesired
	if type(desired) ~= "table" then
		desired = {}
		self._eqolRegDesired = desired
	else
		wipe(desired)
	end

	local function regUnit(ev)
		desired[ev] = "unit"
		local needsRegister = true
		if self.IsEventRegistered then
			local isRegistered, unit1 = self:IsEventRegistered(ev)
			needsRegister = not isRegistered or unit1 ~= unit
		end
		if needsRegister then self:RegisterUnitEvent(ev, unit) end
		registered[ev] = "unit"
	end

	local function reg(ev)
		desired[ev] = "event"
		local needsRegister = true
		if self.IsEventRegistered then
			local isRegistered, unit1 = self:IsEventRegistered(ev)
			needsRegister = not isRegistered or unit1 ~= nil
		end
		if needsRegister then self:RegisterEvent(ev) end
		registered[ev] = "event"
	end

	regUnit("UNIT_CONNECTION")
	reg("PARTY_MEMBER_ENABLE")
	reg("PARTY_MEMBER_DISABLE")
	regUnit("UNIT_HEALTH")
	regUnit("UNIT_MAXHEALTH")
	if self._eqolUFState and self._eqolUFState._wantsTempMaxHealthLoss then regUnit("UNIT_MAX_HEALTH_MODIFIERS_CHANGED") end
	if self._eqolUFState and self._eqolUFState._wantsIncomingHeal then
		regUnit("UNIT_HEAL_PREDICTION")
	end
	if self._eqolUFState and self._eqolUFState._wantsAbsorb then
		regUnit("UNIT_ABSORB_AMOUNT_CHANGED")
		regUnit("UNIT_HEAL_ABSORB_AMOUNT_CHANGED")
	end

	local powerH = cfg and cfg.powerHeight or 0
	local wantsPower = self._eqolUFState and self._eqolUFState._wantsPower
	if wantsPower == nil then wantsPower = true end
	if powerH and powerH > 0 and wantsPower then
		regUnit("UNIT_POWER_UPDATE")
		regUnit("UNIT_MAXPOWER")
		regUnit("UNIT_DISPLAYPOWER")
	end

	regUnit("UNIT_NAME_UPDATE")
	if self._eqolUFState and self._eqolUFState._wantsPortrait then
		regUnit("UNIT_PORTRAIT_UPDATE")
		regUnit("UNIT_MODEL_CHANGED")
		regUnit("UNIT_ENTERED_VEHICLE")
		regUnit("UNIT_EXITED_VEHICLE")
		regUnit("UNIT_EXITING_VEHICLE")
	end
	regUnit("UNIT_FLAGS")
	regUnit("UNIT_CTR_OPTIONS")
	regUnit("UNIT_OTHER_PARTY_CHANGED")
	local wantsLevel = self._eqolUFState and self._eqolUFState._wantsLevel
	if not wantsLevel and UFHelper and UFHelper.textModeUsesLevel then
		local hc = cfg and cfg.health or {}
		local pcfg = cfg and cfg.power or {}
		local dbc = cfg and cfg.dataBar or {}
		if UFHelper.textModeUsesLevel(hc.textLeft) or UFHelper.textModeUsesLevel(hc.textCenter) or UFHelper.textModeUsesLevel(hc.textRight) then
			wantsLevel = true
		elseif UFHelper.textModeUsesLevel(pcfg.textLeft) or UFHelper.textModeUsesLevel(pcfg.textCenter) or UFHelper.textModeUsesLevel(pcfg.textRight) then
			wantsLevel = true
		elseif dbc.enabled == true and (UFHelper.textModeUsesLevel(dbc.textLeft) or UFHelper.textModeUsesLevel(dbc.textCenter) or UFHelper.textModeUsesLevel(dbc.textRight)) then
			wantsLevel = true
		end
	end
	if wantsLevel then regUnit("UNIT_LEVEL") end

	if self._eqolUFState and (self._eqolUFState._wantsAuras or self._eqolUFState._wantsDispelTint or self._eqolUFState._wantsHealerBuffPlacement) then regUnit("UNIT_AURA") end
	if self._eqolUFState and self._eqolUFState._wantsRangeFade then regUnit("UNIT_IN_RANGE_UPDATE") end
	if self._eqolUFState and self._eqolUFState._wantsAggroHighlight then
		regUnit("UNIT_THREAT_SITUATION_UPDATE")
		regUnit("UNIT_THREAT_LIST_UPDATE")
	end
	regUnit("INCOMING_SUMMON_CHANGED")
	regUnit("INCOMING_RESURRECT_CHANGED")
	regUnit("UNIT_PHASE")

	for ev in pairs(registered) do
		if not desired[ev] then
			if self.UnregisterEvent then self:UnregisterEvent(ev) end
			registered[ev] = nil
		end
	end
	wipe(desired)
end

function GF.UnitButton_OnAttributeChanged(self, name, value)
	if name ~= "unit" then return end
	if value == nil or value == "" then
		if self._eqolUseSecureUnitAttribute == true then return end
		GF:UnitButton_ClearUnit(self)
		GF:UpdateAll(self)
		return
	end
	GF:UnitButton_EvaluateUnit(self, false)
end

local function dispatchUnitHealth(btn, unit)
	local st = getState(btn)
	GF:UpdateHealthValue(btn, unit, st)
end
local function dispatchUnitAbsorb(btn, unit)
	local st = getState(btn)
	GF:UpdateHealthValue(btn, unit, st)
end
local function dispatchUnitHealAbsorb(btn, unit)
	local st = getState(btn)
	GF:UpdateHealthValue(btn, unit, st)
end
local function dispatchUnitPower(btn, unit)
	local st = getState(btn)
	GF:UpdatePowerValue(btn, unit, st)
end
local function dispatchUnitDisplayPower(btn) GF:UpdatePower(btn) end
local function dispatchUnitName(btn, unit)
	local st = getState(btn)
	GF:CacheUnitStatic(btn)
	GF:UpdateName(btn, unit, st)
	GF:UpdateStatusText(btn, unit, st)
	GF:UpdateHealthStyle(btn, unit, st)
	GF:UpdateLevel(btn, unit, st)
end
local function dispatchUnitLevel(btn, unit)
	local st = getState(btn)
	GF:UpdateLevel(btn, unit, st)
	GF:UpdateHealthValue(btn, unit, st)
	GF:UpdatePowerValue(btn, unit, st)
end
local function dispatchUnitConnection(btn, unit)
	local st = getState(btn)
	GF:RefreshConnectionDependentVisuals(btn, unit, st)
end
local function dispatchUnitFlags(btn, unit)
	local st = getState(btn)
	GF:UpdateHealthStyle(btn, unit, st)
	GF:UpdateHealthValue(btn, unit, st)
	GF:UpdateStatusText(btn, unit, st)
	GF:UpdateName(btn, unit, st)
	GF:UpdatePhaseIcon(btn)
end
local function dispatchUnitRange(btn, _, inRange) GF:UpdateRange(btn, inRange) end
local function dispatchUnitAura(btn, _, updateInfo) GF:UpdateAuras(btn, updateInfo) end
local function dispatchUnitPortrait(btn, unit)
	local st = getState(btn)
	GF:UpdatePortrait(btn, unit, st)
end

function GF.DispatchUnitThreat(btn) GF:UpdateHighlightState(btn) end

local UNIT_DISPATCH = {
	UNIT_HEALTH = dispatchUnitHealth,
	UNIT_MAXHEALTH = dispatchUnitHealth,
	UNIT_HEAL_PREDICTION = function(btn, unit)
		local st = getState(btn)
		GF:UpdateHealthValue(btn, unit, st)
	end,
	UNIT_ABSORB_AMOUNT_CHANGED = dispatchUnitAbsorb,
	UNIT_HEAL_ABSORB_AMOUNT_CHANGED = dispatchUnitHealAbsorb,
	UNIT_MAX_HEALTH_MODIFIERS_CHANGED = dispatchUnitHealth,
	UNIT_POWER_UPDATE = dispatchUnitPower,
	UNIT_MAXPOWER = dispatchUnitPower,
	UNIT_DISPLAYPOWER = dispatchUnitDisplayPower,
	UNIT_NAME_UPDATE = dispatchUnitName,
	UNIT_LEVEL = dispatchUnitLevel,
	UNIT_CONNECTION = dispatchUnitConnection,
	PARTY_MEMBER_ENABLE = dispatchUnitConnection,
	PARTY_MEMBER_DISABLE = dispatchUnitConnection,
	UNIT_PORTRAIT_UPDATE = dispatchUnitPortrait,
	UNIT_MODEL_CHANGED = dispatchUnitPortrait,
	UNIT_ENTERED_VEHICLE = dispatchUnitPortrait,
	UNIT_EXITED_VEHICLE = dispatchUnitPortrait,
	UNIT_EXITING_VEHICLE = dispatchUnitPortrait,
	UNIT_FLAGS = dispatchUnitFlags,
	UNIT_CTR_OPTIONS = function(btn) GF:UpdatePhaseIcon(btn) end,
	UNIT_OTHER_PARTY_CHANGED = function(btn) GF:UpdatePhaseIcon(btn) end,
	UNIT_IN_RANGE_UPDATE = dispatchUnitRange,
	UNIT_AURA = dispatchUnitAura,
	UNIT_THREAT_SITUATION_UPDATE = GF.DispatchUnitThreat,
	UNIT_THREAT_LIST_UPDATE = GF.DispatchUnitThreat,
	INCOMING_SUMMON_CHANGED = function(btn)
		GF:UpdateSummonIcon(btn)
		GF:UpdateResurrectIcon(btn)
	end,
	INCOMING_RESURRECT_CHANGED = function(btn) GF:UpdateResurrectIcon(btn) end,
	UNIT_PHASE = function(btn) GF:UpdatePhaseIcon(btn) end,
}

function GF.UnitButton_OnEvent(self, event, unit, ...)
	if not isFeatureEnabled() then return end
	if self.IsVisible and not self:IsVisible() then
		local st = self._eqolUFState
		if st then st._missedHiddenEvent = true end
		return
	end
	local u = getUnit(self)
	if not u or (unit and unit ~= u) then return end

	local fn = UNIT_DISPATCH[event]
	if fn then fn(self, u, ...) end
end

function GF.UnitButton_OnEnter(self)
	local st = getState(self)
	if st then
		st._hovered = true
		GF:UpdateHighlightState(self)
	end
	if not shouldShowTooltip(self) then return end
	local unit = getUnit(self)
	if not unit then return end
	if not GameTooltip or GameTooltip:IsForbidden() then return end
	local useEditMode = st and st._tooltipUseEditMode
	if useEditMode == nil then
		local kind = (self and self._eqolGroupKind) or "party"
		local cfg = (self and (self._eqolCfg or getCfg(kind))) or getCfg(kind)
		local tc = cfg and cfg.tooltip or nil
		local def = (DEFAULTS[kind] and DEFAULTS[kind].tooltip) or (DEFAULTS.party and DEFAULTS.party.tooltip) or nil
		useEditMode = tc and tc.useEditMode
		if useEditMode == nil then useEditMode = def and def.useEditMode end
	end
	if useEditMode == true and GameTooltip_SetDefaultAnchor then
		GameTooltip_SetDefaultAnchor(GameTooltip, self)
	else
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
	end
	GameTooltip:SetUnit(unit)
	GameTooltip:Show()
end

function GF.UnitButton_OnLeave(self)
	local st = getState(self)
	if st then
		st._hovered = false
		GF:UpdateHighlightState(self)
	end
	if GameTooltip and not GameTooltip:IsForbidden() then GameTooltip:Hide() end
end

local function cancelQueuedGroupIndicatorRefresh()
	local timer = GF._groupIndicatorRefreshTimer
	if timer and timer.Cancel then timer:Cancel() end
	GF._groupIndicatorRefreshTimer = nil
end

local function queueGroupIndicatorRefresh(delay, repeats)
	if not isFeatureEnabled() then return end
	cancelQueuedGroupIndicatorRefresh()
	local wait = tonumber(delay) or 0
	local remaining = tonumber(repeats) or 1
	if remaining < 1 then remaining = 1 end

	local function run()
		GF._groupIndicatorRefreshTimer = nil
		if not isFeatureEnabled() then return end
		if InCombatLockdown and InCombatLockdown() then
			GF._pendingRefresh = true
			return
		end
		GF:RefreshGroupIndicators()
		remaining = remaining - 1
		if remaining > 0 and C_Timer and C_Timer.NewTimer then GF._groupIndicatorRefreshTimer = C_Timer.NewTimer(0.12, run) end
	end

	if C_Timer and C_Timer.NewTimer then
		GF._groupIndicatorRefreshTimer = C_Timer.NewTimer(wait, run)
	else
		run()
	end
end

function GF.GetSecureHeaderLayoutKey(header)
	if not (header and header.GetAttribute) then return "" end
	return table.concat({
		tostring(header:GetAttribute("point") or ""),
		tostring(header:GetAttribute("xOffset") or ""),
		tostring(header:GetAttribute("yOffset") or ""),
		tostring(header:GetAttribute("columnSpacing") or ""),
		tostring(header:GetAttribute("columnAnchorPoint") or ""),
		tostring(header:GetAttribute("maxColumns") or ""),
		tostring(header:GetAttribute("unitsPerColumn") or ""),
	}, "\031")
end

function GF.BuildSecureHeaderLayoutKey(point, xOffset, yOffset, columnSpacing, columnAnchorPoint, maxColumns, unitsPerColumn)
	return table.concat({
		tostring(point or ""),
		tostring(xOffset or ""),
		tostring(yOffset or ""),
		tostring(columnSpacing or ""),
		tostring(columnAnchorPoint or ""),
		tostring(maxColumns or ""),
		tostring(unitsPerColumn or ""),
	}, "\031")
end

function GF.ClearSecureHeaderChildPoints(header)
	if not (header and header.GetAttribute) then return end
	local index = 1
	local child = header:GetAttribute("child" .. index)

	while child do
		if child.ClearAllPoints then child:ClearAllPoints() end
		index = index + 1
		child = header:GetAttribute("child" .. index)
	end
end

function GF.PrepareSecureHeaderLayoutChange(header, nextLayoutKey)
	if not header or not nextLayoutKey then return end
	if InCombatLockdown and InCombatLockdown() then
		header._eqolPendingLayout = true
		return
	end

	local currentLayoutKey = header._eqolLastSecureLayoutKey or GF.GetSecureHeaderLayoutKey(header)
	if currentLayoutKey ~= nextLayoutKey then GF.ClearSecureHeaderChildPoints(header) end
	header._eqolLastSecureLayoutKey = nextLayoutKey
end

local function nudgeHeaderLayout(header)
	if not header then return end
	if InCombatLockdown and InCombatLockdown() then
		header._eqolPendingLayout = true
		return
	end
	local layoutKey = GF.GetSecureHeaderLayoutKey(header)
	header._eqolLastSecureLayoutKey = layoutKey
	header._eqolPendingLayout = nil
	if header._eqolKind == "raid" then queueGroupIndicatorRefresh(0, 4) end
end

local getGrowthStartPoint = GFH.GetGrowthStartPoint

local function isRaidLikeKind(kind) return kind == "raid" or kind == "mt" or kind == "ma" end

local function isSplitRoleKind(kind) return kind == "mt" or kind == "ma" end

local function getSplitRoleFilter(kind)
	if kind == "mt" then return "MAINTANK" end
	if kind == "ma" then return "MAINASSIST" end
	return nil
end

local function ensureAnchor(kind, parent)
	if not kind then return nil end
	GF.anchors = GF.anchors or {}
	local anchor = GF.anchors[kind]
	if anchor then return anchor end

	local name
	if kind == "party" then
		name = "EQOLUFPartyAnchor"
	elseif kind == "raid" then
		name = "EQOLUFRaidAnchor"
	elseif kind == "mt" then
		name = "EQOLUFMTAnchor"
	elseif kind == "ma" then
		name = "EQOLUFMAAnchor"
	end
	if not name then return nil end

	anchor = CreateFrame("Frame", name, parent or UIParent, "BackdropTemplate")
	anchor._eqolKind = kind
	anchor:EnableMouse(false)
	if anchor.SetClampedToScreen then anchor:SetClampedToScreen(true) end
	anchor:SetFrameStrata("MEDIUM")
	anchor:SetFrameLevel(1)

	if anchor.SetBackdrop then
		anchor:SetBackdrop({
			bgFile = "Interface\\Buttons\\WHITE8x8",
			edgeFile = "Interface\\Buttons\\WHITE8x8",
			tile = false,
			edgeSize = 1,
			insets = { left = 0, right = 0, top = 0, bottom = 0 },
		})
		anchor:SetBackdropColor(0, 0, 0, 0.08)
		anchor:SetBackdropBorderColor(0, 0, 0, 0.6)
	end

	anchor:Hide()
	GF.anchors[kind] = anchor
	return anchor
end

function GF:UpdateAnchorSize(kind)
	local cfg = getCfg(kind)
	local anchor = GF.anchors and GF.anchors[kind]
	if not (cfg and anchor) then return end

	local scale = GFH.GetEffectiveScale(UIParent)

	local w = clampNumber(tonumber(cfg.width) or 100, 40, 600, 100)
	local h = clampNumber(tonumber(cfg.height) or 24, 10, 200, 24)
	w = roundToEvenPixel(w, scale)
	h = roundToEvenPixel(h, scale)

	local spacing = roundToPixel(clampNumber(tonumber(cfg.spacing) or 0, 0, GROUP_FRAME_MAX_SPACING, 0), scale)
	local columnSpacing = spacing
	if isRaidLikeKind(kind) then columnSpacing = roundToPixel(clampNumber(tonumber(cfg.columnSpacing) or spacing, 0, 40, spacing), scale) end
	local growthMode, growth = GF.ResolveUnitGrowthDirection(cfg.growth, "DOWN")
	if not GF.SupportsCenterGrowth(kind) and (growthMode == "CENTER_HORIZONTAL" or growthMode == "CENTER_VERTICAL") then growth = growthMode == "CENTER_HORIZONTAL" and "RIGHT" or "DOWN" end

	local unitsPer = 5
	local columns = 1
	if isRaidLikeKind(kind) then
		unitsPer = max(1, floor(clampNumber(tonumber(cfg.unitsPerColumn) or 5, 1, 10, 5) + 0.5))
		columns = max(1, floor(clampNumber(tonumber(cfg.maxColumns) or 8, 1, 10, 8) + 0.5))
	end

	local totalW, totalH
	if growth == "RIGHT" or growth == "LEFT" then
		totalW = w * unitsPer + spacing * max(0, unitsPer - 1)
		totalH = h * columns + columnSpacing * max(0, columns - 1)
	else
		totalW = w * columns + columnSpacing * max(0, columns - 1)
		totalH = h * unitsPer + spacing * max(0, unitsPer - 1)
	end

	if totalW < w then totalW = w end
	if totalH < h then totalH = h end

	totalW = roundToEvenPixel(totalW, scale)
	totalH = roundToEvenPixel(totalH, scale)

	if Pixel and Pixel.SetSize then
		Pixel.SetSize(anchor, totalW, totalH)
	else
		anchor:SetSize(totalW, totalH)
	end
end

local function applyVisibility(header, kind, cfg)
	if not header or not cfg then return end
	local def = DEFAULTS[kind]
	local hideInClientScene = GFH and GFH.ShouldHideInClientScene and GFH.ShouldHideInClientScene(cfg, def)
	local inEdit = isEditModeActive and isEditModeActive()
	local arenaPartyActive = false
	if not inEdit then
		if type(ShouldShowArenaParty) == "function" then
			arenaPartyActive = ShouldShowArenaParty() == true
		else
			arenaPartyActive = (IsActiveBattlefieldArena and IsActiveBattlefieldArena()) and not (C_PvP and C_PvP.IsInBrawl and C_PvP.IsInBrawl())
		end
	end
	local forceClientSceneHide = not inEdit and hideInClientScene and GF._clientSceneActive == true
	if GFH and GFH.ApplyClientSceneAlphaToFrame then GFH.ApplyClientSceneAlphaToFrame(header, forceClientSceneHide) end
	if not RegisterStateDriver then return end
	if InCombatLockdown and InCombatLockdown() then return end

	local cond = "hide"
	if cfg.enabled ~= true then
		cond = "hide"
	elseif header._eqolForceHide or header._eqolSpecialHide or forceClientSceneHide then
		cond = "hide"
	elseif header._eqolForceShow then
		cond = "show"
	elseif arenaPartyActive then
		if kind == "party" then
			cond = "show"
		else
			cond = "hide"
		end
	elseif kind == "party" then
		if cfg.showSolo then
			cond = "[group:raid] hide; show"
		else
			cond = "[group:raid] hide; [group:party] show; hide"
		end
	elseif isRaidLikeKind(kind) then
		cond = "[group:raid] show; hide"
	end

	if header._eqolVisibilityCond == cond then return end
	if UnregisterStateDriver then UnregisterStateDriver(header, "visibility") end
	RegisterStateDriver(header, "visibility", cond)
	header._eqolVisibilityCond = cond
end

local function getRaidGroupHeaderKey(index) return "raidGroup" .. tostring(index) end

function GF:EnsureRaidGroupHeaders()
	GF._raidGroupHeaders = GF._raidGroupHeaders or {}
	local parent = _G.PetBattleFrameHider or UIParent
	for i = 1, 8 do
		local header = GF._raidGroupHeaders[i]
		if not header then
			header = CreateFrame("Frame", "EQOLUFRaidGroupHeader" .. i, parent, "SecureGroupHeaderTemplate")
			header._eqolKind = "raid"
			header._eqolGroupIndex = i
			header:Hide()
			GF._raidGroupHeaders[i] = header
			GF.headers[getRaidGroupHeaderKey(i)] = header
		end
		if header and not header._eqolLayoutHooked then
			header._eqolLayoutHooked = true
			header:HookScript("OnShow", function(self)
				if self._eqolPendingLayout then nudgeHeaderLayout(self) end
			end)
		end
	end
	return GF._raidGroupHeaders
end

function GF:EnsurePreviewFrames(kind)
	if kind ~= "party" and kind ~= "raid" and kind ~= "mt" and kind ~= "ma" then return nil end
	local cfg = getCfg(kind)
	if not (cfg and cfg.enabled == true) then return nil end
	local samples = GF._previewSamples[kind]
	if not (samples and #samples > 0) then return nil end
	if InCombatLockdown and InCombatLockdown() then return nil end
	local anchor = GF.anchors and GF.anchors[kind]
	if not anchor then return nil end
	GF._previewFrames = GF._previewFrames or {}
	local frames = GF._previewFrames[kind]
	if not frames then frames = {} end
	GF._previewFrames[kind] = frames
	for i = 1, #samples do
		local btn = frames[i]
		if not btn then
			btn = CreateFrame("Button", nil, anchor, "EQOLUFGroupUnitButtonTemplate")
			frames[i] = btn
		end
		btn._eqolGroupKind = kind
		btn._eqolPreview = true
		btn._eqolPreviewIndex = i
		-- Preview buttons use synthetic unit assignment via self.unit, not secure header attributes.
		btn._eqolUseSecureUnitAttribute = false
		btn:SetFrameStrata(anchor:GetFrameStrata())
		btn:SetFrameLevel(GF.ClampFrameLevel((anchor:GetFrameLevel() or 1) + 1))
		local st = getState(btn)
		local sample = samples[i] or {}
		st._previewRole = sample.role or "DAMAGER"
		st._previewClass = sample.class
		st._previewName = sample.name or sample.class or "Unit"
		st._previewGroup = sample.group
		st._previewIndex = i
		GF:UnitButton_SetUnit(btn, "player")
	end
	for i = #samples + 1, #frames do
		local btn = frames[i]
		if btn then
			GF:UnitButton_ClearUnit(btn)
			btn:Hide()
		end
		frames[i] = nil
	end
	return frames
end

function GF:UpdatePreviewLayout(kind)
	local frames = GF._previewFrames and GF._previewFrames[kind]
	local anchor = GF.anchors and GF.anchors[kind]
	if not (frames and anchor) then return end
	local cfg = getCfg(kind)
	if not (cfg and cfg.enabled == true) then
		GF:ShowPreviewFrames(kind, false)
		return
	end
	local raidStyle = isRaidLikeKind(kind)
	local sampleLimit = (kind == "raid" and ((GF._previewSampleSize and GF._previewSampleSize[kind]) or 10)) or nil
	local samples = (GFH.BuildPreviewSampleList and GFH.BuildPreviewSampleList(kind, cfg, GF._previewSamples[kind], sampleLimit, 2, 3)) or (GF._previewSamples[kind] or {})
	local growthMode, growth = GF.ResolveUnitGrowthDirection(cfg.growth, "DOWN")
	local centerGrowthActive = GF.IsCenterGrowthMode(kind, cfg)
	GF._previewSampleCount = GF._previewSampleCount or {}
	GF._previewSampleCount[kind] = #samples

	local scale = GFH.GetEffectiveScale(UIParent)

	-- Keep preview frames pixel-perfect as well (otherwise sample-mode text can jitter / drift).
	local w = clampNumber(tonumber(cfg.width) or 100, 40, 600, 100)
	local h = clampNumber(tonumber(cfg.height) or 24, 10, 200, 24)
	w = roundToEvenPixel(w, scale)
	h = roundToEvenPixel(h, scale)

	local spacing = clampNumber(tonumber(cfg.spacing) or 0, 0, GROUP_FRAME_MAX_SPACING, 0)
	spacing = roundToPixel(spacing, scale)

	local startPoint = getGrowthStartPoint(growth)
	local isHorizontal = (growth == "RIGHT" or growth == "LEFT")
	local xSign = (growth == "LEFT") and -1 or 1
	local ySign = (growth == "UP") and 1 or -1
	local unitsPerColumn = 1
	local maxColumns = 1
	local viewportColumns = 1
	local columnSpacing = spacing
	local useGroupedPreview = false
	local groupedPreviewEntries
	local groupedPreviewBlockCount = 0
	local previewGroupSpecs
	local groupGrowth
	local groupsPerRow = 8
	local previewScale = 1
	local sortMethod
	local useGroupedCustomSort = false
	if raidStyle then
		unitsPerColumn = max(1, floor(clampNumber(tonumber(cfg.unitsPerColumn) or 5, 1, 10, 5) + 0.5))
		maxColumns = max(1, floor(clampNumber(tonumber(cfg.maxColumns) or 8, 1, 10, 8) + 0.5))
		viewportColumns = maxColumns
		columnSpacing = roundToPixel(clampNumber(tonumber(cfg.columnSpacing) or spacing, 0, 40, spacing), scale)
		groupGrowth = GF.ResolveRaidCrossGrowth(cfg, growth)
		groupsPerRow = GF.NormalizeRaidGroupsPerRow(cfg.groupsPerRow, DEFAULTS.raid and DEFAULTS.raid.groupsPerRow)
		if kind == "raid" then
			sortMethod = resolveSortMethod(cfg)
			local customSort = GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
			useGroupedCustomSort = (sortMethod == "NAMELIST") and (customSort and customSort.enabled == true)
			useGroupedPreview = GF:IsRaidGroupedLayout(cfg) and (sortMethod ~= "NAMELIST" or useGroupedCustomSort)
			if useGroupedPreview then
				previewGroupSpecs = GF:BuildRaidGroupHeaderSpecs(cfg, sortMethod, useGroupedCustomSort)
				if previewGroupSpecs and #previewGroupSpecs > 0 then
					local fallbackToSampleGroups = false
					if useGroupedCustomSort then
						local neededBlocks = math.max(1, math.ceil(#samples / math.max(1, unitsPerColumn)))
						-- Preview mode should reflect the requested sample size, not the currently populated live raid groups.
						fallbackToSampleGroups = #previewGroupSpecs ~= neededBlocks
					else
						local sampleGroups = {}
						local sampleGroupSeen = {}
						for _, sample in ipairs(samples) do
							local group = tonumber(sample and sample.group)
							if group and group >= 1 and group <= 8 and not sampleGroupSeen[group] then
								sampleGroupSeen[group] = true
								sampleGroups[#sampleGroups + 1] = group
							end
						end
						if #sampleGroups > 0 then
							if #previewGroupSpecs ~= #sampleGroups then
								fallbackToSampleGroups = true
							else
								for idx = 1, #sampleGroups do
									local specGroup = tonumber(previewGroupSpecs[idx] and previewGroupSpecs[idx].group)
									if specGroup ~= sampleGroups[idx] then
										fallbackToSampleGroups = true
										break
									end
								end
							end
						end
					end
					if fallbackToSampleGroups then previewGroupSpecs = nil end
				end
				local unitIsHorizontal = (growth == "LEFT" or growth == "RIGHT")
				local groupIsHorizontal = (groupGrowth == "LEFT" or groupGrowth == "RIGHT")
				if unitIsHorizontal == groupIsHorizontal then
					startPoint = getGrowthStartPoint(groupGrowth)
				elseif unitIsHorizontal then
					local horizontalPoint = (growth == "LEFT") and "RIGHT" or "LEFT"
					local verticalPoint = (groupGrowth == "UP") and "BOTTOM" or "TOP"
					startPoint = verticalPoint .. horizontalPoint
				else
					local horizontalPoint = (groupGrowth == "LEFT") and "RIGHT" or "LEFT"
					local verticalPoint = (growth == "UP") and "BOTTOM" or "TOP"
					startPoint = verticalPoint .. horizontalPoint
				end
			end
		end
	end
	if raidStyle and not useGroupedPreview and not centerGrowthActive then startPoint = GF.GetRaidLayoutStartPoint(growth, groupGrowth) end
	local maxShown
	if raidStyle and useGroupedPreview then
		if useGroupedCustomSort then
			groupedPreviewEntries = {}
			local cursor = 1
			local specs = previewGroupSpecs or {}
			for blockIndex = 1, #specs do
				local spec = specs[blockIndex]
				for unitIndex = 1, unitsPerColumn do
					local sample = samples[cursor]
					if not sample then break end
					groupedPreviewEntries[#groupedPreviewEntries + 1] = {
						sample = sample,
						group = tonumber(spec and spec.group) or blockIndex,
						groupIndex = blockIndex,
						unitIndex = unitIndex,
					}
					cursor = cursor + 1
				end
			end
			groupedPreviewBlockCount = #specs
			if groupedPreviewBlockCount == 0 and #samples > 0 then
				for i, sample in ipairs(samples) do
					local blockIndex = floor((i - 1) / unitsPerColumn) + 1
					local unitIndex = ((i - 1) % unitsPerColumn) + 1
					groupedPreviewEntries[#groupedPreviewEntries + 1] = {
						sample = sample,
						group = blockIndex,
						groupIndex = blockIndex,
						unitIndex = unitIndex,
					}
				end
				groupedPreviewBlockCount = math.ceil(#samples / max(1, unitsPerColumn))
			end
		else
			local buckets = {}
			for _, sample in ipairs(samples) do
				local group = tonumber(sample and sample.group)
				if group and group >= 1 and group <= 8 then
					local bucket = buckets[group]
					if not bucket then
						bucket = {}
						buckets[group] = bucket
					end
					bucket[#bucket + 1] = sample
				end
			end
			local orderedGroups = {}
			local specs = previewGroupSpecs or {}
			for _, spec in ipairs(specs) do
				local group = tonumber(spec and spec.group)
				if group and buckets[group] then orderedGroups[#orderedGroups + 1] = group end
			end
			if #orderedGroups == 0 then
				for group = 1, 8 do
					if buckets[group] then orderedGroups[#orderedGroups + 1] = group end
				end
			end
			local blockCount = #orderedGroups
			groupedPreviewBlockCount = blockCount
			groupedPreviewEntries = {}
			for blockIndex = 1, blockCount do
				local group = orderedGroups[blockIndex]
				local bucket = buckets[group]
				if bucket then
					for unitIndex = 1, min(#bucket, unitsPerColumn) do
						groupedPreviewEntries[#groupedPreviewEntries + 1] = {
							sample = bucket[unitIndex],
							group = group,
							groupIndex = blockIndex,
							unitIndex = unitIndex,
						}
					end
				end
			end
		end
		local perGroupWidth, perGroupHeight
		if isHorizontal then
			perGroupWidth = w * unitsPerColumn + spacing * max(0, unitsPerColumn - 1)
			perGroupHeight = h
		else
			perGroupWidth = w
			perGroupHeight = h * unitsPerColumn + spacing * max(0, unitsPerColumn - 1)
		end
		local runtimeGroupsPerLine = min(groupedPreviewBlockCount, groupsPerRow)
		local viewportGroupsPerLine = min(viewportColumns, groupsPerRow)
		previewScale = (GFH.GetRaidViewportScaleForGroups and GFH.GetRaidViewportScaleForGroups(groupGrowth, perGroupWidth, perGroupHeight, columnSpacing, viewportGroupsPerLine, runtimeGroupsPerLine))
			or 1
		maxShown = min(#frames, #groupedPreviewEntries)
	elseif raidStyle then
		local limit = #samples
		if kind == "raid" then limit = tonumber(GF._previewSampleSize and GF._previewSampleSize[kind]) or 10 end
		maxShown = min(#frames, limit, #samples)
		local requiredColumns = max(1, math.ceil(maxShown / max(1, unitsPerColumn)))
		previewScale = (GFH.GetRaidViewportScaleForColumns and GFH.GetRaidViewportScaleForColumns(growth, w, h, spacing, columnSpacing, viewportColumns, requiredColumns)) or 1
	else
		maxShown = min(#frames, #samples)
	end
	local visualScale = GF.NormalizeRaidAutoFitScale(previewScale)
	local visualW, visualH = w, h
	local visualSpacing = spacing
	local visualColumnSpacing = columnSpacing
	if raidStyle then
		local visualRuntimeCount = 1
		if useGroupedPreview then
			visualRuntimeCount = min(groupedPreviewBlockCount, groupsPerRow)
		else
			visualRuntimeCount = max(1, math.ceil((maxShown or 0) / max(1, unitsPerColumn)))
		end
		visualW, visualH, _, _, visualColumnSpacing = GF.GetRaidAutoFitLayoutMetrics(growth, w, h, 0, 0, columnSpacing, visualScale, scale, viewportColumns, visualRuntimeCount)
	else
		visualW = roundToEvenPixel(max(1, visualW), scale)
		visualH = roundToEvenPixel(max(1, visualH), scale)
		visualColumnSpacing = roundToPixel(visualColumnSpacing, scale)
	end
	visualSpacing = roundToPixel(visualSpacing, scale)
	local previewAnchorPoint = startPoint
	if centerGrowthActive then previewAnchorPoint = GF.GetCenterGrowthRelativePoint(growth) end
	local previewCenterOffsetX, previewCenterOffsetY = 0, 0
	if centerGrowthActive and maxShown and maxShown > 0 then
		local totalSpan
		local crossGrowth
		local viewportCrossSpan
		local contentCrossSpan
		if raidStyle and useGroupedPreview then
			local groupWidth, groupHeight
			local baseGroupWidth, baseGroupHeight
			if isHorizontal then
				groupWidth = visualW * unitsPerColumn + visualSpacing * max(0, unitsPerColumn - 1)
				groupHeight = visualH
				baseGroupWidth = w * unitsPerColumn + spacing * max(0, unitsPerColumn - 1)
				baseGroupHeight = h
			else
				groupWidth = visualW
				groupHeight = visualH * unitsPerColumn + visualSpacing * max(0, unitsPerColumn - 1)
				baseGroupWidth = w
				baseGroupHeight = h * unitsPerColumn + spacing * max(0, unitsPerColumn - 1)
			end
			groupWidth = roundToEvenPixel(groupWidth, scale)
			groupHeight = roundToEvenPixel(groupHeight, scale)
			baseGroupWidth = roundToEvenPixel(baseGroupWidth, scale)
			baseGroupHeight = roundToEvenPixel(baseGroupHeight, scale)
			if isHorizontal then
				if groupGrowth == "LEFT" or groupGrowth == "RIGHT" then
					local runtimeGroupsPerLine = min(groupedPreviewBlockCount, groupsPerRow)
					totalSpan = groupWidth * runtimeGroupsPerLine + visualColumnSpacing * max(0, runtimeGroupsPerLine - 1)
				else
					totalSpan = groupWidth
				end
			else
				if groupGrowth == "UP" or groupGrowth == "DOWN" then
					local runtimeGroupsPerLine = min(groupedPreviewBlockCount, groupsPerRow)
					totalSpan = groupHeight * runtimeGroupsPerLine + visualColumnSpacing * max(0, runtimeGroupsPerLine - 1)
				else
					totalSpan = groupHeight
				end
			end
			crossGrowth = groupGrowth
			if groupedPreviewBlockCount > 0 then
				local runtimeGroupLines = max(1, math.ceil(groupedPreviewBlockCount / groupsPerRow))
				if isHorizontal then
					viewportCrossSpan = baseGroupHeight * viewportColumns + columnSpacing * max(0, viewportColumns - 1)
					contentCrossSpan = groupHeight * runtimeGroupLines + visualColumnSpacing * max(0, runtimeGroupLines - 1)
				else
					viewportCrossSpan = baseGroupWidth * viewportColumns + columnSpacing * max(0, viewportColumns - 1)
					contentCrossSpan = groupWidth * runtimeGroupLines + visualColumnSpacing * max(0, runtimeGroupLines - 1)
				end
			end
		elseif raidStyle then
			local perLineCount = min(maxShown, unitsPerColumn)
			local runtimeColumns = max(1, math.ceil(maxShown / max(1, unitsPerColumn)))
			if isHorizontal then
				totalSpan = perLineCount * visualW + visualSpacing * max(0, perLineCount - 1)
				crossGrowth = "DOWN"
				viewportCrossSpan = h * viewportColumns + columnSpacing * max(0, viewportColumns - 1)
				contentCrossSpan = visualH * runtimeColumns + visualColumnSpacing * max(0, runtimeColumns - 1)
			else
				totalSpan = perLineCount * visualH + visualSpacing * max(0, perLineCount - 1)
				crossGrowth = "RIGHT"
				viewportCrossSpan = w * viewportColumns + columnSpacing * max(0, viewportColumns - 1)
				contentCrossSpan = visualW * runtimeColumns + visualColumnSpacing * max(0, runtimeColumns - 1)
			end
		else
			totalSpan = isHorizontal and (maxShown * visualW + visualSpacing * max(0, maxShown - 1)) or (maxShown * visualH + visualSpacing * max(0, maxShown - 1))
		end
		previewCenterOffsetX, previewCenterOffsetY = GF.ComputeCenterGrowthOffsetFromSpan(growth, totalSpan, scale)
		if crossGrowth and viewportCrossSpan and contentCrossSpan then
			local crossOffsetX, crossOffsetY = GF.ComputeViewportCenteringOffset(crossGrowth, viewportCrossSpan, contentCrossSpan, scale)
			previewCenterOffsetX = previewCenterOffsetX + crossOffsetX
			previewCenterOffsetY = previewCenterOffsetY + crossOffsetY
		end
	end
	for i, btn in ipairs(frames) do
		if btn then
			local groupedEntry = groupedPreviewEntries and groupedPreviewEntries[i]
			local sample = groupedEntry and groupedEntry.sample or samples[i]
			if i > maxShown or not sample then
				btn._eqolFitScale = 1
				if btn.SetScale then btn:SetScale(1) end
				btn:Hide()
			else
				local st = getState(btn)
				if st then
					st._previewRole = sample.role or "DAMAGER"
					st._previewClass = sample.class
					st._previewName = sample.name or sample.class or "Unit"
					st._previewGroup = (groupedEntry and groupedEntry.group) or sample.group
					st._previewIndex = (groupedEntry and groupedEntry.unitIndex) or i
				end
				btn._eqolGroupKind = kind
				btn._eqolCfg = cfg
				btn._eqolFitScale = (raidStyle and visualScale < 1) and visualScale or nil
				updateButtonConfig(btn, cfg)
				if btn.SetScale then btn:SetScale(1) end
				if Pixel and Pixel.SetSize then
					Pixel.SetSize(btn, visualW, visualH, 1, 1)
				else
					btn:SetSize(visualW, visualH)
				end
				btn:ClearAllPoints()
				if raidStyle and groupedEntry then
					local groupIndex = groupedEntry.groupIndex - 1
					local groupSlot = groupIndex % groupsPerRow
					local groupLine = floor(groupIndex / groupsPerRow)
					local unitIndex = groupedEntry.unitIndex - 1
					local groupOffsetX, groupOffsetY = 0, 0
					local groupWidth, groupHeight
					if isHorizontal then
						groupWidth = visualW * unitsPerColumn + visualSpacing * max(0, unitsPerColumn - 1)
						groupHeight = visualH
					else
						groupWidth = visualW
						groupHeight = visualH * unitsPerColumn + visualSpacing * max(0, unitsPerColumn - 1)
					end
					groupWidth = roundToPixel(groupWidth, scale)
					groupHeight = roundToPixel(groupHeight, scale)
					if groupGrowth == "LEFT" then
						groupOffsetX = roundToPixel(groupSlot * (groupWidth + visualColumnSpacing) * -1, scale)
						groupOffsetY = roundToPixel(groupLine * (groupHeight + visualColumnSpacing) * -1, scale)
					elseif groupGrowth == "UP" then
						groupOffsetY = roundToPixel(groupSlot * (groupHeight + visualColumnSpacing), scale)
						groupOffsetX = roundToPixel(groupLine * (groupWidth + visualColumnSpacing), scale)
					elseif groupGrowth == "RIGHT" then
						groupOffsetX = roundToPixel(groupSlot * (groupWidth + visualColumnSpacing), scale)
						groupOffsetY = roundToPixel(groupLine * (groupHeight + visualColumnSpacing) * -1, scale)
					else
						groupOffsetY = roundToPixel(groupSlot * (groupHeight + visualColumnSpacing) * -1, scale)
						groupOffsetX = roundToPixel(groupLine * (groupWidth + visualColumnSpacing), scale)
					end
					local unitOffsetX, unitOffsetY = 0, 0
					if isHorizontal then
						unitOffsetX = roundToPixel(unitIndex * (visualW + visualSpacing) * xSign, scale)
					else
						unitOffsetY = roundToPixel(unitIndex * (visualH + visualSpacing) * ySign, scale)
					end
					if Pixel and Pixel.SetPoint then
						Pixel.SetPoint(btn, startPoint, anchor, previewAnchorPoint, previewCenterOffsetX + groupOffsetX + unitOffsetX, previewCenterOffsetY + groupOffsetY + unitOffsetY)
					else
						btn:SetPoint(startPoint, anchor, previewAnchorPoint, previewCenterOffsetX + groupOffsetX + unitOffsetX, previewCenterOffsetY + groupOffsetY + unitOffsetY)
					end
				elseif raidStyle then
					local idx = i - 1
					local row = idx % unitsPerColumn
					local col = floor(idx / unitsPerColumn)
					if isHorizontal then
						if Pixel and Pixel.SetPoint then
							Pixel.SetPoint(
								btn,
								startPoint,
								anchor,
								previewAnchorPoint,
								previewCenterOffsetX + roundToPixel(row * (visualW + visualSpacing) * xSign, scale),
								previewCenterOffsetY + roundToPixel(col * (visualH + visualColumnSpacing) * ((groupGrowth == "UP") and 1 or -1), scale)
							)
						else
							btn:SetPoint(
								startPoint,
								anchor,
								previewAnchorPoint,
								previewCenterOffsetX + roundToPixel(row * (visualW + visualSpacing) * xSign, scale),
								previewCenterOffsetY + roundToPixel(col * (visualH + visualColumnSpacing) * ((groupGrowth == "UP") and 1 or -1), scale)
							)
						end
					else
						if Pixel and Pixel.SetPoint then
							Pixel.SetPoint(
								btn,
								startPoint,
								anchor,
								previewAnchorPoint,
								previewCenterOffsetX + roundToPixel(col * (visualW + visualColumnSpacing) * ((groupGrowth == "LEFT") and -1 or 1), scale),
								previewCenterOffsetY + roundToPixel(row * (visualH + visualSpacing) * ySign, scale)
							)
						else
							btn:SetPoint(
								startPoint,
								anchor,
								previewAnchorPoint,
								previewCenterOffsetX + roundToPixel(col * (visualW + visualColumnSpacing) * ((groupGrowth == "LEFT") and -1 or 1), scale),
								previewCenterOffsetY + roundToPixel(row * (visualH + visualSpacing) * ySign, scale)
							)
						end
					end
				else
					if isHorizontal then
						if Pixel and Pixel.SetPoint then
							Pixel.SetPoint(btn, startPoint, anchor, previewAnchorPoint, previewCenterOffsetX + roundToPixel((i - 1) * (visualW + visualSpacing) * xSign, scale), previewCenterOffsetY)
						else
							btn:SetPoint(startPoint, anchor, previewAnchorPoint, previewCenterOffsetX + roundToPixel((i - 1) * (visualW + visualSpacing) * xSign, scale), previewCenterOffsetY)
						end
					else
						if Pixel and Pixel.SetPoint then
							Pixel.SetPoint(btn, startPoint, anchor, previewAnchorPoint, previewCenterOffsetX, previewCenterOffsetY + roundToPixel((i - 1) * (visualH + visualSpacing) * ySign, scale))
						else
							btn:SetPoint(startPoint, anchor, previewAnchorPoint, previewCenterOffsetX, previewCenterOffsetY + roundToPixel((i - 1) * (visualH + visualSpacing) * ySign, scale))
						end
					end
				end
				GF:CacheUnitStatic(btn)
				GF:LayoutAuras(btn)
				if btn.unit then GF:UnitButton_RegisterUnitEvents(btn, btn.unit) end
				if btn._eqolUFState then
					GF:LayoutButton(btn)
					GF:UpdateAll(btn)
					if btn._eqolPreview then GF:UpdateAuras(btn) end
				end
				btn:Show()
			end
		end
	end
	if kind == "raid" then GF:RefreshGroupIndicators() end
end

function GF:ShowPreviewFrames(kind, show)
	local frames = GF._previewFrames and GF._previewFrames[kind]
	if not frames then return end
	local maxShown = #frames
	if kind == "raid" then maxShown = min(maxShown, tonumber(GF._previewSampleSize and GF._previewSampleSize[kind]) or 10) end
	local sampleCount = GF._previewSampleCount and GF._previewSampleCount[kind]
	if sampleCount then maxShown = min(maxShown, sampleCount) end
	for i, btn in ipairs(frames) do
		if btn then
			if show and i <= maxShown then
				if not btn.unit then GF:UnitButton_SetUnit(btn, "player") end
				btn:Show()
			else
				GF:UnitButton_ClearUnit(btn)
				btn:Hide()
			end
		end
	end
	if kind == "raid" then GF:RefreshGroupIndicators() end
end

local function getCustomSortEditor()
	if GF._customSortEditor then return GF._customSortEditor end
	if not (GFH and GFH.CreateCustomSortEditor) then return nil end
	local function getEditorKind()
		local kind = GF._customSortEditorKind
		if kind ~= "party" and kind ~= "raid" then kind = "raid" end
		return kind
	end
	GF._customSortEditor = GFH.CreateCustomSortEditor({
		roleTokens = GFH.ROLE_TOKENS,
		classTokens = GFH.CLASS_TOKENS,
		subtitle = "Drag entries to reorder. Applies to Party and Raid custom sorting.",
		getOrders = function()
			local cfg = getCfg(getEditorKind())
			local custom = GFH.EnsureCustomSortConfig(cfg)
			return custom and custom.roleOrder, custom and custom.classOrder
		end,
		onReorder = function(listKey, order)
			local kind = getEditorKind()
			local cfg = getCfg(kind)
			local custom = GFH.EnsureCustomSortConfig(cfg)
			if not custom then return end
			if listKey == "role" then
				custom.roleOrder = order
			else
				custom.classOrder = order
			end
			GF:RefreshCustomSortNameList(kind)
			if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
		end,
	})
	return GF._customSortEditor
end

function GF:ToggleCustomSortEditor(kind)
	if not isEditModeActive() and not (EditMode and EditMode.IsAvailable and EditMode:IsAvailable()) then return end
	kind = tostring(kind or "raid"):lower()
	if kind ~= "party" and kind ~= "raid" then kind = "raid" end
	GF._customSortEditorKind = kind
	local cfg = getCfg(kind)
	if not cfg then return end
	local custom = GFH.EnsureCustomSortConfig(cfg)
	if custom and (custom.enabled ~= true or resolveSortMethod(cfg) ~= "NAMELIST") then
		custom.enabled = true
		cfg.sortMethod = "NAMELIST"
		if EditMode and EditMode.SetValue then
			local editModeId = EDITMODE_IDS and EDITMODE_IDS[kind] or nil
			if editModeId then
				EditMode:SetValue(editModeId, "sortMethod", "CUSTOM", nil, true)
				EditMode:SetValue(editModeId, "customSortEnabled", true, nil, true)
			end
		end
		GF:ApplyHeaderAttributes(kind)
		if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
		if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
	end

	local editor = getCustomSortEditor()
	if not editor then return end
	if editor.Title and editor.Title.SetText then
		local label = (kind == "party" and (PARTY or "Party")) or (RAID or "Raid")
			editor.Title:SetText((L["customSortEditorTitle"]):format(label))
	end
	local sameKind = editor._eqolKind == kind
	editor._eqolKind = kind
	if editor:IsShown() and sameKind then
		editor:Hide()
	else
		editor:Refresh()
		editor:Show()
	end
	if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
end

function GF:RefreshHealerBuffPlacement(kind)
	if not UF.GroupFramesHealerBuffs then return end
	if kind ~= nil then
		kind = tostring(kind):lower()
		if kind ~= "party" and kind ~= "raid" then kind = nil end
	end
	local db = DB or ensureDB()
	local hbm = UF.GroupFramesHealerBuffs
	if hbm and hbm.MarkPlacementDirty and db then
		local function markKind(value)
			local cfg = db[value]
			if cfg and cfg.healerBuffPlacement then hbm.MarkPlacementDirty(cfg.healerBuffPlacement) end
		end
		if kind then
			markKind(kind)
		else
			markKind("party")
			markKind("raid")
		end
	end
	if db and db.party and db.raid then kind = nil end
	if hbm and hbm.InvalidateKind then
		if kind then
			hbm.InvalidateKind(kind)
		else
			hbm.InvalidateKind("party")
			hbm.InvalidateKind("raid")
		end
	end

	local function normalizeKind(value)
		value = tostring(value or ""):lower()
		if value == "mt" or value == "ma" then return "raid" end
		return value
	end

	local function eachChild(header, fn)
		if not (header and fn and header.GetAttribute) then return end
		local index = 1
		local child = header:GetAttribute("child" .. index)
		while child do
			fn(child, index)
			index = index + 1
			child = header:GetAttribute("child" .. index)
		end
	end

	local function refreshButton(btn)
		if not btn then return end
		local btnKind = normalizeKind(btn._eqolGroupKind or "")
		if kind and btnKind ~= kind then return end
		local cfgKind = btn._eqolGroupKind or kind or "party"
		local cfg = btn._eqolCfg or getCfg(cfgKind)
		if cfg then
			btn._eqolCfg = cfg
			updateButtonConfig(btn, cfg)
		end
		if btn._eqolUFState then
			if UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.LayoutButton then UF.GroupFramesHealerBuffs.LayoutButton(btn) end
			GF:UpdateAuras(btn)
		end
	end

	for _, header in pairs(GF.headers or {}) do
		eachChild(header, function(child) refreshButton(child) end)
	end
	if not kind or kind == "raid" then
		for _, header in ipairs(GF._raidGroupHeaders or EMPTY) do
			if header and not header._eqolSpecialHide then eachChild(header, function(child) refreshButton(child) end) end
		end
	end
	for frameKind, frames in pairs(GF._previewFrames or EMPTY) do
		local normalizedKind = normalizeKind(frameKind)
		if not kind or kind == normalizedKind then
			for i = 1, #frames do
				refreshButton(frames[i])
			end
		end
	end
end

function GF:ToggleHealerBuffPlacementEditor(kind)
	local editor = UF and UF.GroupFramesHealerBuffEditor
	if not (editor and editor.Toggle) then return end
	kind = tostring(kind or "raid"):lower()
	if kind ~= "party" and kind ~= "raid" then kind = "raid" end
	if GF.IsHealerBuffPlacementSuppressedByBlizzard(kind, getCfg(kind)) then
		if editor.IsShown and editor:IsShown() and editor.Hide then editor:Hide() end
		return
	end
	editor:Toggle(kind)
	if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
end

function GF:ToggleGlobalAuraIgnoreEditor(kind)
	local editor = UF and UF.GlobalAuraIgnore
	if not (editor and editor.ToggleEditor) then return end
	kind = tostring(kind or "raid"):lower()
	if kind ~= "party" and kind ~= "raid" then kind = "raid" end
	editor:ToggleEditor(kind)
	if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
end

function GF:SetEditModeSampleFrames(kind, show)
	if kind ~= "party" and kind ~= "raid" and kind ~= "mt" and kind ~= "ma" then return end
	GF._editModeSampleFrames = GF._editModeSampleFrames or {}
	local enabled = show == true
	if GF._editModeSampleFrames[kind] == enabled then return end
	GF._editModeSampleFrames[kind] = enabled
	if not isEditModeActive() then return end
	GF:EnsureHeaders()
	local header = GF.headers and GF.headers[kind]
	if not header then return end
	if enabled and not (InCombatLockdown and InCombatLockdown()) then
		if kind == "raid" then
			GF._previewSampleSize = GF._previewSampleSize or {}
			if not GF._previewSampleSize[kind] then GF._previewSampleSize[kind] = 10 end
		end
		header._eqolForceShow = nil
		header._eqolForceHide = true
		GF._previewActive = GF._previewActive or {}
		GF._previewActive[kind] = true
		GF:EnsurePreviewFrames(kind)
		GF:UpdatePreviewLayout(kind)
		GF:ShowPreviewFrames(kind, true)
	else
		if GF._previewActive then GF._previewActive[kind] = nil end
		GF:ShowPreviewFrames(kind, false)
		header._eqolForceHide = nil
		header._eqolForceShow = true
	end
	GF:ApplyHeaderAttributes(kind)
end

function GF:ToggleEditModeSampleFrames(kind)
	local enabled = GF._editModeSampleFrames and GF._editModeSampleFrames[kind]
	GF:SetEditModeSampleFrames(kind, enabled ~= true)
end

function GF:CycleEditModeSampleSize(kind)
	if kind ~= "raid" then return end
	local sizes = { 10, 20, 30, 40 }
	GF._previewSampleSize = GF._previewSampleSize or {}
	local current = tonumber(GF._previewSampleSize[kind]) or 0
	local idx = 0
	for i, size in ipairs(sizes) do
		if size == current then
			idx = i
			break
		end
	end
	local nextSize = sizes[(idx % #sizes) + 1]
	GF._previewSampleSize[kind] = nextSize
	if isEditModeActive() and GF._previewActive and GF._previewActive[kind] then
		GF:EnsurePreviewFrames(kind)
		GF:UpdatePreviewLayout(kind)
		GF:ShowPreviewFrames(kind, true)
	end
end

local function forEachChild(header, fn)
	if not header or not fn then return end
	if not header.GetAttribute then return end

	local index = 1
	local child = header:GetAttribute("child" .. index)
	while child do
		fn(child, index)
		index = index + 1
		child = header:GetAttribute("child" .. index)
	end
end

local function syncHeaderChild(child, kind, cfg, frameW, frameH, fitScale)
	if not (child and cfg) then return end

	child._eqolGroupKind = kind
	child._eqolUseSecureUnitAttribute = true
	child._eqolCfg = cfg
	fitScale = GF.NormalizeRaidAutoFitScale(fitScale)
	child._eqolFitScale = (kind == "raid" and fitScale < 1) and fitScale or nil
	updateButtonConfig(child, cfg)
	if frameW and frameH and child.SetSize then
		local inCombat = InCombatLockdown and InCombatLockdown()
		if not inCombat then
			local w = tonumber(frameW) or 0
			local h = tonumber(frameH) or 0
			if w > 0 and h > 0 then
				local forceLiveSize = GF.ForceLiveGroupButtonSize(child, kind, cfg, w, h)
				if not forceLiveSize then
					local cw, ch = child:GetSize()
					if abs((cw or 0) - w) > 0.01 or abs((ch or 0) - h) > 0.01 then
						if Pixel and Pixel.SetSize then
							Pixel.SetSize(child, w, h, 1, 1)
						else
							child:SetSize(w, h)
						end
					end
				end
			end
		end
	end
	GF:LayoutAuras(child)
	if child.unit then GF:UnitButton_RegisterUnitEvents(child, child.unit) end
	if child._eqolUFState then
		GF:CacheUnitStatic(child)
		GF:LayoutButton(child)
		GF:UpdateAll(child)
	end
end

local function refreshAllAuras()
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateAuras(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateAuras(btn) end
			end
		end
	end
end

local function refreshAllPrivateAuras()
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdatePrivateAuras(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdatePrivateAuras(btn) end
			end
		end
	end
end

function GF.ClearAuraSamplesOnButton(btn)
	local st = btn and getState(btn)
	if not st then return end
	if st.buffContainer then st.buffContainer:Hide() end
	if st.debuffContainer then st.debuffContainer:Hide() end
	if st.externalContainer then st.externalContainer:Hide() end
	GF:ClearBlizzardAuraContainer(btn)
	hideAuraButtons(st.buffButtons, 1)
	hideAuraButtons(st.debuffButtons, 1)
	hideAuraButtons(st.externalButtons, 1)
	st._auraSampleActive = nil
	GF:UpdateDispelTint(btn, nil, nil)
	if st._healerBuffPlacementActive and UF.GroupFramesHealerBuffs and UF.GroupFramesHealerBuffs.ClearButton then
		UF.GroupFramesHealerBuffs.ClearButton(btn)
		st._healerBuffPlacementActive = nil
	end
end

function GF:ClearEditModeAuraSamples(kind)
	local function normalized(value)
		value = tostring(value or ""):lower()
		if value == "mt" or value == "ma" then return "raid" end
		return value
	end
	local targetKind = kind and normalized(kind)
	for headerKind, header in pairs(GF.headers or {}) do
		if not targetKind or targetKind == normalized(headerKind) then forEachChild(header, GF.ClearAuraSamplesOnButton) end
	end
	if GF._previewFrames then
		for frameKind, frames in pairs(GF._previewFrames) do
			if not targetKind or targetKind == normalized(frameKind) then
				for _, btn in ipairs(frames) do
					GF.ClearAuraSamplesOnButton(btn)
				end
			end
		end
	end
end

function GF:SetEditModeSampleAuras(show)
	local enabled = show ~= false
	if GF._editModeSampleAuras == enabled then return end
	GF._editModeSampleAuras = enabled
	if not isEditModeActive() then return end
	refreshAllAuras()
	refreshAllPrivateAuras()
end

function GF:ToggleEditModeSampleAuras() GF:SetEditModeSampleAuras(GF._editModeSampleAuras == false) end

function GF:SetEditModeStatusText(show)
	local enabled = show ~= false
	if GF._editModeSampleStatusText == enabled then return end
	GF._editModeSampleStatusText = enabled
	if not isEditModeActive() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateStatusText(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateStatusText(btn) end
			end
		end
	end
end

function GF:ToggleEditModeStatusText() GF:SetEditModeStatusText(GF._editModeSampleStatusText == false) end

function GF:RefreshRoleIcons()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateRoleIcon(child) end
		end)
	end
end

function GF:ScheduleRoleIconRefresh()
	if GF._roleIconRefreshQueued then return end
	GF._roleIconRefreshQueued = true
	local function refresh()
		GF._roleIconRefreshQueued = nil
		GF:RefreshRoleIcons()
	end
	RunNextFrame(refresh)
end

function GF:RefreshGroupIcons()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateGroupIcons(child) end
		end)
	end
end

function GF:RefreshStatusText()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateStatusText(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateStatusText(btn) end
			end
		end
	end
end

function GF.GetUnitConnectedState(unit)
	if not unit or not UnitIsConnected then return nil end
	return GFH.UnsecretBool(UnitIsConnected(unit))
end

function GF.RememberUnitConnectedState(st, unit)
	if not st then return nil end
	local connected = GF.GetUnitConnectedState(unit)
	st._lastConnectedState = connected
	return connected
end

function GF:RefreshConnectionDependentVisuals(frame, unit, st)
	if not (frame and unit) then return end
	st = st or getState(frame)
	GF:CacheUnitStatic(frame)
	GF:UpdateHealthStyle(frame, unit, st)
	GF:UpdateHealthValue(frame, unit, st)
	GF:UpdatePowerValue(frame, unit, st)
	GF:UpdatePortrait(frame, unit, st)
	GF:UpdateName(frame, unit, st)
	GF:UpdateStatusText(frame, unit, st)
	GF:UpdateLevel(frame, unit, st)
	GF:UpdateStatusIcons(frame)
	GF:UpdateRange(frame, nil, unit, st)
	GF.RememberUnitConnectedState(st, unit)
end

function GF:RefreshConnectionState(unit)
	if not isFeatureEnabled() then return 0 end
	local refreshed = 0
	local unitToken = (type(unit) == "string") and unit or nil
	local function refreshChild(child)
		if not (child and child._eqolUFState) then return end
		local childUnit = getUnit(child)
		if not childUnit then return end
		if unitToken and childUnit ~= unitToken then return end

		local st = getState(child)
		GF:RefreshConnectionDependentVisuals(child, childUnit, st)
		refreshed = refreshed + 1
	end

	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child) refreshChild(child) end)
	end

	if GF._raidGroupHeaders then
		for _, header in ipairs(GF._raidGroupHeaders) do
			if header and not header._eqolSpecialHide then forEachChild(header, function(child) refreshChild(child) end) end
		end
	end

	return refreshed
end

function GF:RefreshConnectionStateIfChanged(unit)
	if not isFeatureEnabled() then return 0 end
	local refreshed = 0
	local unitToken = (type(unit) == "string") and unit or nil
	local function refreshChild(child)
		if not (child and child._eqolUFState) then return end
		local childUnit = getUnit(child)
		if not childUnit then return end
		if unitToken and childUnit ~= unitToken then return end

		local st = getState(child)
		local connected = GF.GetUnitConnectedState(childUnit)
		if st._lastConnectedState == connected then return end

		GF:RefreshConnectionDependentVisuals(child, childUnit, st)
		refreshed = refreshed + 1
	end

	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child) refreshChild(child) end)
	end

	if GF._raidGroupHeaders then
		for _, header in ipairs(GF._raidGroupHeaders) do
			if header and not header._eqolSpecialHide then forEachChild(header, function(child) refreshChild(child) end) end
		end
	end

	return refreshed
end

function GF:CancelConnectionRefreshTicker()
	local ticker = self._connectionRefreshTicker
	if ticker and ticker.Cancel then ticker:Cancel() end
	self._connectionRefreshTicker = nil
	self._connectionRefreshPasses = nil
end

function GF:CancelConnectionRecheckTimer(unit)
	local timers = self._connectionRecheckTimers
	if not timers then return end
	local key = unit or "__all__"
	local timer = timers[key]
	if timer and timer.Cancel then timer:Cancel() end
	timers[key] = nil
	if not next(timers) then self._connectionRecheckTimers = nil end
end

function GF:CancelAllConnectionRecheckTimers()
	local timers = self._connectionRecheckTimers
	if not timers then return end
	for key, timer in pairs(timers) do
		if timer and timer.Cancel then timer:Cancel() end
		timers[key] = nil
	end
	self._connectionRecheckTimers = nil
end

function GF:ScheduleConnectionRecheck(unit)
	if not (C_Timer and C_Timer.NewTimer) then return end
	local key = unit or "__all__"
	self:CancelConnectionRecheckTimer(unit)
	self._connectionRecheckTimers = self._connectionRecheckTimers or {}
	self._connectionRecheckTimers[key] = C_Timer.NewTimer(1, function()
		GF:CancelConnectionRecheckTimer(unit)
		if not isFeatureEnabled() then return end
		GF:RefreshConnectionStateIfChanged(unit)
	end)
end

function GF:RunConnectionRefreshPass()
	if not isFeatureEnabled() then return end
	self:RefreshConnectionStateIfChanged()
end

function GF:ScheduleConnectionRefresh()
	self:CancelConnectionRefreshTicker()
	if not (C_Timer and C_Timer.NewTicker) then
		self:RunConnectionRefreshPass()
		return
	end
	-- Connection and roster state can settle over multiple frames after relog/reload.
	self._connectionRefreshPasses = 0
	self._connectionRefreshTicker = C_Timer.NewTicker(0.25, function()
		GF._connectionRefreshPasses = (GF._connectionRefreshPasses or 0) + 1
		if not isFeatureEnabled() then
			GF:CancelConnectionRefreshTicker()
			return
		end
		GF:RunConnectionRefreshPass()
		if (GF._connectionRefreshPasses or 0) >= 8 then GF:CancelConnectionRefreshTicker() end
	end)
end

function GF:RefreshRangeFade()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateRange(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateRange(btn) end
			end
		end
	end
end

function GF:RefreshGroupIndicators()
	if not isFeatureEnabled() then return end
	local cfg = getCfg("raid")
	if not cfg then return end
	local def = DEFAULTS.raid or {}
	local header = GF.headers and GF.headers.raid
	local sortMethod = resolveSortMethod(cfg)
	local customSort = GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
	local useGroupedHeaders = GF:IsRaidGroupedLayout(cfg) and (sortMethod ~= "NAMELIST" or (customSort and customSort.enabled == true))
	if useGroupedHeaders and GF._raidGroupHeaders then
		for _, gh in ipairs(GF._raidGroupHeaders) do
			if gh and not gh._eqolSpecialHide then
				local frames = {}
				forEachChild(gh, function(child)
					if child then frames[#frames + 1] = child end
				end)
				updateGroupIndicatorsForFrames(gh, frames, cfg, def, false, gh._eqolDisplayGroup)
			end
		end
	elseif header then
		local frames = {}
		forEachChild(header, function(child)
			if child then frames[#frames + 1] = child end
		end)
		updateGroupIndicatorsForFrames(header, frames, cfg, def, false)
	end
	if GF._previewFrames and GF._previewFrames.raid then
		local parent = (GF.anchors and GF.anchors.raid) or header
		if parent then updateGroupIndicatorsForFrames(parent, GF._previewFrames.raid, cfg, def, true) end
	end
end

function GF:RefreshDispelTint()
	if not isFeatureEnabled() then return end
	local inEdit = isEditModeActive()
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then
				if inEdit or child._eqolPreview then
					GF:UpdateDispelTint(child, nil, nil, true)
				else
					local st = getState(child)
					local cache = st and getAuraCache(st, "debuff")
					GF:UpdateDispelTint(child, cache, AURA_FILTERS.dispellable, nil, AURA_KIND_DISPEL)
				end
				GF:UpdatePrivateAuraDispelContainer(child)
			end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then
					GF:UpdateDispelTint(btn, nil, nil, true)
					GF:UpdatePrivateAuraDispelContainer(btn)
				end
			end
		end
	end
end

function GF:RefreshTargetHighlights()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateHighlightState(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateHighlightState(btn) end
			end
		end
	end
end

function GF:RefreshTextStyles()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then
				updateButtonConfig(child, child._eqolCfg)
				if child._eqolUFState then
					GF:LayoutButton(child)
					GF:UpdateAll(child)
				end
			end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then
					updateButtonConfig(btn, btn._eqolCfg)
					if btn._eqolUFState then
						GF:LayoutButton(btn)
						GF:UpdateAll(btn)
					end
				end
			end
		end
	end
	GF:RefreshGroupIndicators()
end

function GF:RefreshRaidIcons()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateRaidIcon(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateRaidIcon(btn) end
			end
		end
	end
end

function GF:RefreshStatusIcons(event)
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateStatusIcons(child, event) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateStatusIcons(btn, event) end
			end
		end
	end
end

function GF:RefreshReadyCheckIcons(event)
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateReadyCheckIcon(child, event) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateReadyCheckIcon(btn, event) end
			end
		end
	end
end

function GF:RefreshNames(options)
	if not isFeatureEnabled() then return end
	local skipLayout = options and options.skipLayout == true
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then
				updateButtonConfig(child, child._eqolCfg)
				if child._eqolUFState then
					if not skipLayout then GF:LayoutButton(child) end
					GF:UpdateName(child)
				end
			end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then
					updateButtonConfig(btn, btn._eqolCfg)
					if btn._eqolUFState then
						if not skipLayout then GF:LayoutButton(btn) end
						GF:UpdateName(btn)
					end
				end
			end
		end
	end
end

function GF:RefreshPowerVisibility()
	if not isFeatureEnabled() then return end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then
				updateButtonConfig(child, child._eqolCfg)
				if child.unit then GF:UnitButton_RegisterUnitEvents(child, child.unit) end
				GF:UpdatePower(child)
			end
		end)
	end
end

function GF:RefreshClientSceneVisibility()
	local pendingCombatRefresh = InCombatLockdown and InCombatLockdown()
	for kind, header in pairs(GF.headers or {}) do
		local cfg = getCfg(kind)
		if header and cfg then applyVisibility(header, kind, cfg) end
	end
	if GF._raidGroupHeaders then
		local cfg = getCfg("raid")
		for _, header in ipairs(GF._raidGroupHeaders) do
			if header and cfg then applyVisibility(header, "raid", cfg) end
		end
	end
	if pendingCombatRefresh then
		GF:MarkPendingHeaderRefresh("party")
		GF:MarkPendingHeaderRefresh("raid")
		GF:MarkPendingHeaderRefresh("mt")
		GF:MarkPendingHeaderRefresh("ma")
	end
end

function GF:UpdateHealthColorMode(kind)
	if not isFeatureEnabled() then return end
	if kind then
		local cfg = getCfg(kind)
		if cfg then sanitizeHealthColorMode(cfg) end
	end
	for _, header in pairs(GF.headers or {}) do
		forEachChild(header, function(child)
			if child then GF:UpdateHealthStyle(child) end
		end)
	end
	if GF._previewFrames then
		for _, frames in pairs(GF._previewFrames) do
			for _, btn in ipairs(frames) do
				if btn then GF:UpdateHealthStyle(btn) end
			end
		end
	end
end

function GF.NormalizeRuntimeSortDir(value) return (GFH and GFH.NormalizeSortDir and GFH.NormalizeSortDir(value)) or ((tostring(value or ""):upper() == "DESC") and "DESC" or "ASC") end

function GF.BuildPartyRuntimeSortState(cfg)
	local centerGrowthActive = GF.IsPartyCenterGrowthMode(cfg)
	local runtimeSortMethod = centerGrowthActive and "NAMELIST" or resolveSortMethod(cfg)
	local runtimeSortDir = GF.NormalizeRuntimeSortDir(cfg and cfg.sortDir)
	local nameList

	if runtimeSortMethod == "NAMELIST" then
		if centerGrowthActive then
			nameList = GF.BuildPartyCenterGrowthNameList(cfg)
		else
			nameList = GFH.BuildCustomSortNameList(cfg, "party")
			if nameList == "" then nameList = nil end
			if not nameList then runtimeSortMethod = GF.GetSafeFallbackSortMethod(DEFAULTS.party and DEFAULTS.party.sortMethod) end
		end
	end

	return {
		centerGrowthActive = centerGrowthActive,
		sortMethod = runtimeSortMethod,
		sortDir = runtimeSortDir,
		nameList = nameList,
	}
end

function GF.BuildRaidRuntimeSortState(cfg)
	local configuredSortMethod = resolveSortMethod(cfg)
	local runtimeSortMethod = configuredSortMethod
	local runtimeSortDir = GF.NormalizeRuntimeSortDir(cfg and cfg.sortDir)
	local customSort = GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
	local useGroupedCustom = isGroupCustomLayout(cfg) and customSort and customSort.enabled == true
	local nameList
	local visibleCount = (GFH.GetCurrentRaidUnitCount and GFH.GetCurrentRaidUnitCount()) or 0
	local groupedSpecs

	if useGroupedCustom then
		groupedSpecs = GF:BuildDenseCustomGroupSpecs(cfg)
		runtimeSortMethod = GF.GetSafeFallbackSortMethod(DEFAULTS.raid and DEFAULTS.raid.sortMethod)
	elseif configuredSortMethod == "NAMELIST" then
		nameList = GFH.BuildCustomSortNameList(cfg, "raid")
		if nameList == "" then nameList = nil end
		if nameList then
			visibleCount = GF.CountCsvTokens(nameList)
		else
			runtimeSortMethod = GF.GetSafeFallbackSortMethod(DEFAULTS.raid and DEFAULTS.raid.sortMethod)
		end
	end

	return {
		configuredSortMethod = configuredSortMethod,
		sortMethod = runtimeSortMethod,
		sortDir = runtimeSortDir,
		nameList = nameList,
		useGroupedCustom = useGroupedCustom,
		groupedSpecs = groupedSpecs,
		visibleCount = visibleCount,
	}
end

function GF:IsCustomRosterSortActive(kind)
	if kind ~= "party" and kind ~= "raid" then return false end
	local db = DB or ensureDB()
	local cfg = db and db[kind]
	if not cfg then return false end
	local raw = cfg.sortMethod
	local method = tostring(raw or ""):upper()
	if method == "CUSTOM" or method == "NAMELIST" then return true end
	local custom = cfg.customSort
	return custom and custom.enabled == true and (raw == nil or raw == "")
end

function GF:HasCustomRosterSort()
	return GF:IsCustomRosterSortActive("party") or GF:IsCustomRosterSortActive("raid")
end

function GF:RefreshCustomSortNameList(kind)
	if not isFeatureEnabled() then return end
	kind = kind or "raid"
	if kind ~= "raid" and kind ~= "party" then return end
	local cfg = getCfg(kind)
	local header = GF.headers and GF.headers[kind]
	if not header then return end
	if InCombatLockdown and InCombatLockdown() then
		GF:MarkPendingSortRefresh(kind)
		return
	end
	if kind == "party" then
		local state = GF.BuildPartyRuntimeSortState(cfg)
		GF:SetHeaderAttributeIfChanged(header, "sortMethod", state.sortMethod)
		GF:SetHeaderAttributeIfChanged(header, "sortDir", state.sortDir)
		GF:SetHeaderAttributeIfChanged(header, "nameList", state.nameList)
		if state.centerGrowthActive and GF.anchors and GF.anchors.party then
			local _, growthDir = GF.ResolveUnitGrowthDirection(cfg and cfg.growth, "DOWN")
			local p = getGrowthStartPoint(growthDir)
			local rp = GF.GetPartyCenterGrowthRelativePoint(growthDir)
			local x, y = GF.ComputePartyCenterGrowthAnchorOffset(cfg, growthDir)
			header:ClearAllPoints()
			if Pixel and Pixel.SetPoint then
				Pixel.SetPoint(header, p, GF.anchors.party, rp, x, y)
			else
				header:SetPoint(p, GF.anchors.party, rp, x, y)
			end
			if header.IsShown and header:IsShown() then
				nudgeHeaderLayout(header)
			else
				header._eqolPendingLayout = true
			end
		end
		return
	end
	local state = GF.BuildRaidRuntimeSortState(cfg)
	GF:SetHeaderAttributeIfChanged(header, "sortMethod", state.sortMethod)
	GF:SetHeaderAttributeIfChanged(header, "sortDir", state.sortDir)
	if state.useGroupedCustom then
		if not GF._raidGroupHeaders then GF:EnsureRaidGroupHeaders() end
		if GF._raidGroupHeaders then
			for i, gh in ipairs(GF._raidGroupHeaders) do
				if gh then
					local spec = state.groupedSpecs and state.groupedSpecs[i]
					local specSortMethod = spec and tostring(spec.sortMethod or "INDEX"):upper() or "INDEX"
					local nameList = spec and spec.nameList
					if specSortMethod == "NAMELIST" then
						if not nameList or nameList == "" then nameList = EMPTY_NAMELIST_TOKEN end
					else
						nameList = nil
					end
					GF:SetHeaderAttributeIfChanged(gh, "sortMethod", specSortMethod)
					GF:SetHeaderAttributeIfChanged(gh, "sortDir", state.sortDir)
					GF:SetHeaderAttributeIfChanged(gh, "nameList", nameList)
				end
			end
		end
		GF:SetHeaderAttributeIfChanged(header, "nameList", nil)
	else
		GF:SetHeaderAttributeIfChanged(header, "nameList", state.nameList)
		if GF._raidGroupHeaders then
			local groupHeaderSortMethod = state.sortMethod
			if groupHeaderSortMethod == "NAMELIST" then groupHeaderSortMethod = GF.GetSafeFallbackSortMethod(DEFAULTS.raid and DEFAULTS.raid.sortMethod) end
			for _, gh in ipairs(GF._raidGroupHeaders) do
				if gh then
					GF:SetHeaderAttributeIfChanged(gh, "sortMethod", groupHeaderSortMethod)
					GF:SetHeaderAttributeIfChanged(gh, "sortDir", state.sortDir)
					GF:SetHeaderAttributeIfChanged(gh, "nameList", nil)
				end
			end
		end
	end
end

local function syncRaidGroupHeaderChildren(header, cfg, layout)
	forEachChild(header, function(child) syncHeaderChild(child, "raid", cfg, layout and layout.w, layout and layout.h, layout and layout.fitScale) end)
end

function GF.UpdateHeaderChildLayoutKey(header, key)
	if not header then return false end
	if header._eqolChildLayoutKey == key then return false end
	header._eqolChildLayoutKey = key
	return true
end

local function applyRaidGroupHeaders(cfg, layout, groupSpecs, forceShow, forceHide, maxGroups, options)
	local headers = GF:EnsureRaidGroupHeaders()
	local anchor = GF.anchors and GF.anchors.raid
	if not (headers and anchor) then return end
	local skipChildSync = options and options.skipChildSync == true
	groupSpecs = groupSpecs or {}
	local maxIndex = tonumber(maxGroups)
	if maxIndex == nil then maxIndex = #groupSpecs end
	if maxIndex < 0 then maxIndex = 0 end
	if maxIndex > 8 then maxIndex = 8 end
	if maxIndex > #groupSpecs then maxIndex = #groupSpecs end
	local groupScale = GF.NormalizeRaidAutoFitScale(layout and layout.fitScale or layout and layout.groupScale)

	for i = 1, 8 do
		local header = headers[i]
		if header then
			header._eqolFitScale = 1
			if header.SetScale then header:SetScale(1) end
			local spec = groupSpecs[i]
			local active = (i <= maxIndex) and (spec ~= nil)
			header._eqolForceShow = forceShow
			header._eqolForceHide = forceHide
			header._eqolSpecialHide = not active

			if active then
				local function setAttr(key, value) GF:SetHeaderAttributeIfChanged(header, key, value) end
				local specSortMethod = tostring(spec.sortMethod or "INDEX"):upper()
				header._eqolDisplayGroup = tonumber(spec.group) or i
				setAttr("showParty", false)
				setAttr("showRaid", true)
				setAttr("showPlayer", true)
				setAttr("showSolo", false)
				setAttr("groupBy", nil)
				setAttr("sortDir", cfg.sortDir or "ASC")
				setAttr("unitsPerColumn", layout.unitsPerColumn)
				setAttr("maxColumns", 1)
				setAttr("minWidth", layout.minWidth)
				setAttr("minHeight", layout.minHeight)

				if specSortMethod == "NAMELIST" then
					local nameList = spec.nameList
					if not nameList or nameList == "" then nameList = EMPTY_NAMELIST_TOKEN end
					setAttr("groupFilter", nil)
					setAttr("roleFilter", nil)
					setAttr("strictFiltering", false)
					setAttr("sortMethod", "NAMELIST")
					setAttr("nameList", nameList)
				else
					local roleFilter = cfg.roleFilter
					if roleFilter == "" then roleFilter = nil end
					if specSortMethod ~= "NAME" and specSortMethod ~= "INDEX" then specSortMethod = "INDEX" end
					setAttr("groupFilter", tostring(spec.group or i))
					setAttr("roleFilter", roleFilter)
					setAttr("strictFiltering", cfg.strictFiltering == true)
					setAttr("sortMethod", specSortMethod)
					setAttr("nameList", nil)
				end

				GF.PrepareSecureHeaderLayoutChange(
					header,
					GF.BuildSecureHeaderLayoutKey(layout.point, layout.xOffset, layout.yOffset, layout.columnSpacing, layout.columnAnchorPoint, 1, layout.unitsPerColumn)
				)
				setAttr("point", layout.point)
				setAttr("xOffset", layout.xOffset)
				setAttr("yOffset", layout.yOffset)
				setAttr("columnSpacing", layout.columnSpacing)
				setAttr("columnAnchorPoint", layout.columnAnchorPoint)
				setAttr("template", "EQOLUFGroupUnitButtonTemplate")
				setAttr("initialConfigFunction", layout.initConfigFunction)

				header:ClearAllPoints()
				local unitGrowth = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(layout.growth, "DOWN")) or "DOWN"
				local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
				local groupGrowth
				if GFH.ResolveGroupGrowthDirection then
					groupGrowth = GFH.ResolveGroupGrowthDirection(layout.groupGrowth, unitGrowth, defaultGroupGrowth)
				else
					groupGrowth = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(layout.groupGrowth, nil)) or ((unitGrowth == "RIGHT" or unitGrowth == "LEFT") and "DOWN" or "RIGHT")
				end
				local groupStartPoint = (GFH.GetGroupGrowthStartPoint and GFH.GetGroupGrowthStartPoint(groupGrowth)) or getGrowthStartPoint(groupGrowth)
				local anchorRelativePoint = layout and layout.centerRelativePoint or groupStartPoint
				local anchorOffsetX = tonumber(layout and layout.centerOffsetX) or 0
				local anchorOffsetY = tonumber(layout and layout.centerOffsetY) or 0
				local groupsPerRow = GF.NormalizeRaidGroupsPerRow(layout and layout.groupsPerRow, 8)
				local groupSlot = (i - 1) % groupsPerRow
				local groupLine = floor((i - 1) / groupsPerRow)
				local spacing = roundToPixel(layout.columnSpacing or 0, layout.scale)
				local perHeaderW = roundToPixel((layout and layout.perHeaderW) or (layout and layout.w) or 0, layout and layout.scale)
				local perHeaderH = roundToPixel((layout and layout.perHeaderH) or (layout and layout.h) or 0, layout and layout.scale)
				local groupOffsetX, groupOffsetY = 0, 0
				if groupGrowth == "LEFT" then
					groupOffsetX = roundToPixel(groupSlot * (perHeaderW + spacing) * -1, layout.scale)
					groupOffsetY = roundToPixel(groupLine * (perHeaderH + spacing) * -1, layout.scale)
				elseif groupGrowth == "UP" then
					groupOffsetY = roundToPixel(groupSlot * (perHeaderH + spacing), layout.scale)
					groupOffsetX = roundToPixel(groupLine * (perHeaderW + spacing), layout.scale)
				elseif groupGrowth == "RIGHT" then
					groupOffsetX = roundToPixel(groupSlot * (perHeaderW + spacing), layout.scale)
					groupOffsetY = roundToPixel(groupLine * (perHeaderH + spacing) * -1, layout.scale)
				else
					groupOffsetY = roundToPixel(groupSlot * (perHeaderH + spacing) * -1, layout.scale)
					groupOffsetX = roundToPixel(groupLine * (perHeaderW + spacing), layout.scale)
				end
				if Pixel and Pixel.SetPoint then
					Pixel.SetPoint(header, groupStartPoint, anchor, anchorRelativePoint, anchorOffsetX + groupOffsetX, anchorOffsetY + groupOffsetY)
				else
					header:SetPoint(groupStartPoint, anchor, anchorRelativePoint, anchorOffsetX + groupOffsetX, anchorOffsetY + groupOffsetY)
				end
			end

			applyVisibility(header, "raid", cfg)

			if active and header.IsShown and header:IsShown() and header.Show then header:Show() end

			local childLayoutKey
			if active then
				childLayoutKey = string.format(
					"raid-group|%.3f|%.3f|%.3f|%d",
					tonumber(layout and layout.w) or 0,
					tonumber(layout and layout.h) or 0,
					tonumber(groupScale) or 1,
					tonumber(header._eqolDisplayGroup) or 0
				)
			end
			local layoutChanged = GF.UpdateHeaderChildLayoutKey(header, childLayoutKey)
			if active and (not skipChildSync or layoutChanged) then syncRaidGroupHeaderChildren(header, cfg, layout) end

			if header.IsShown and header:IsShown() then
				nudgeHeaderLayout(header)
			else
				header._eqolPendingLayout = true
			end

			if not active and header.Hide then header:Hide() end
			if not active then header._eqolDisplayGroup = nil end
		end
	end
end

function GF:ApplyHeaderAttributes(kind, options)
	local cfg = getCfg(kind)
	local header = GF.headers[kind]
	if not header then return end
	if not isFeatureEnabled() then return end
	if InCombatLockdown and InCombatLockdown() then
		GF:MarkPendingHeaderRefresh(kind)
		return
	end

	local spacing = clampNumber(tonumber(cfg.spacing) or 0, 0, GROUP_FRAME_MAX_SPACING, 0)
	local growthMode, growth = GF.ResolveUnitGrowthDirection(cfg.growth, "DOWN")
	if not GF.SupportsCenterGrowth(kind) and (growthMode == "CENTER_HORIZONTAL" or growthMode == "CENTER_VERTICAL") then growthMode = growth end
	if cfg.growth ~= growthMode then cfg.growth = growthMode end
	local centerGrowthActive = GF.IsCenterGrowthMode(kind, cfg)
	local scale = GFH.GetEffectiveScale(UIParent)
	spacing = roundToPixel(spacing, scale)
	local raidUnitsPerColumn
	local raidMaxColumns
	local raidRuntimeMaxColumns
	local raidViewportScale = 1
	local raidVisibleCount = 0
	local raidGroupSpecs
	local useGroupHeaders = false
	local useGroupedCustomSort = false
	local splitRoleViewerAllowed = true
	local sortMethod
	local rawGroupBy
	local db = DB or ensureDB()
	local raidFramesEnabled = db and db.raid and db.raid.enabled == true
	local function setAttr(key, value) GF:SetHeaderAttributeIfChanged(header, key, value) end
	local skipChildSync = options and options.skipChildSync == true

	if kind == "party" then
		local partySortState = GF.BuildPartyRuntimeSortState(cfg)
		cfg.groupBy = nil
		cfg.groupingOrder = nil
		setAttr("showParty", true)
		setAttr("showRaid", false)
		setAttr("showPlayer", cfg.showPlayer and true or false)
		setAttr("showSolo", cfg.showSolo and true or false)
		setAttr("groupBy", nil)
		setAttr("groupingOrder", nil)
		setAttr("groupFilter", nil)
		setAttr("roleFilter", nil)
		setAttr("strictFiltering", false)
		sortMethod = partySortState.sortMethod
		local sortDir = partySortState.sortDir
		setAttr("sortMethod", sortMethod)
		setAttr("sortDir", sortDir)
		setAttr("nameList", partySortState.nameList)
		setAttr("maxColumns", 1)
		setAttr("unitsPerColumn", 5)
	elseif kind == "raid" then
		local raidSortState = GF.BuildRaidRuntimeSortState(cfg)
		raidVisibleCount = raidSortState.visibleCount
		setAttr("showParty", false)
		setAttr("showRaid", true)
		setAttr("showPlayer", true)
		setAttr("showSolo", false)
		local groupingOrder = cfg.groupingOrder
		if groupingOrder == "" then groupingOrder = nil end
		rawGroupBy = cfg.groupBy
		local normalizedGroupBy = resolveGroupByValue(cfg, DEFAULTS.raid) or "GROUP"
		if rawGroupBy and tostring(rawGroupBy):upper() == "CLASS" then groupingOrder = nil end
		setAttr("groupingOrder", groupingOrder or GFH.GROUP_ORDER)
		local groupFilter = cfg.groupFilter
		if groupFilter == "" then groupFilter = nil end
		if rawGroupBy and tostring(rawGroupBy):upper() == "CLASS" then groupFilter = nil end
		setAttr("groupFilter", groupFilter)
		local roleFilter = cfg.roleFilter
		if roleFilter == "" then roleFilter = nil end
		setAttr("roleFilter", roleFilter)
		setAttr("strictFiltering", cfg.strictFiltering == true)
		sortMethod = raidSortState.sortMethod
		local customSort = GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
		useGroupedCustomSort = raidSortState.useGroupedCustom and (customSort and customSort.enabled == true)
		setAttr("sortMethod", sortMethod)
		setAttr("sortDir", raidSortState.sortDir)
		setAttr("nameList", raidSortState.nameList)
		setAttr("groupBy", normalizedGroupBy)
		raidUnitsPerColumn = clampNumber(tonumber(cfg.unitsPerColumn) or 5, 1, 10, 5)
		raidMaxColumns = clampNumber(tonumber(cfg.maxColumns) or 8, 1, 10, 8)
		setAttr("unitsPerColumn", raidUnitsPerColumn)
		raidRuntimeMaxColumns = raidMaxColumns
		setAttr("maxColumns", raidRuntimeMaxColumns)
		useGroupHeaders = GF:IsRaidGroupedLayout(cfg) and (sortMethod ~= "NAMELIST" or useGroupedCustomSort)
		raidGroupSpecs = raidSortState.groupedSpecs
		local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
		if GFH.ResolveGroupGrowthDirection then
			cfg.groupGrowth = GFH.ResolveGroupGrowthDirection(cfg.groupGrowth, growth, defaultGroupGrowth)
		else
			cfg.groupGrowth = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(cfg.groupGrowth, nil)) or ((growth == "RIGHT" or growth == "LEFT") and "DOWN" or "RIGHT")
		end
	elseif isSplitRoleKind(kind) then
		cfg.hideSelf = nil
		cfg.playerFirst = nil
		local viewerRoleFilter = GF.NormalizeViewerRoleFilter(cfg.viewerRoleFilter)
		splitRoleViewerAllowed = isEditModeActive() or viewerRoleFilter == "ALL" or GF.GetPlayerEffectiveRoleKey() == viewerRoleFilter
		setAttr("showParty", false)
		setAttr("showRaid", splitRoleViewerAllowed)
		setAttr("showPlayer", true)
		setAttr("showSolo", false)
		setAttr("groupingOrder", nil)
		setAttr("groupFilter", splitRoleViewerAllowed and getSplitRoleFilter(kind) or nil)
		setAttr("groupBy", nil)
		setAttr("nameList", nil)
		setAttr("roleFilter", nil)
		setAttr("strictFiltering", false)
		setAttr("sortMethod", "NAME")
		setAttr("sortDir", "ASC")
		raidUnitsPerColumn = clampNumber(tonumber(cfg.unitsPerColumn) or ((DEFAULTS[kind] and DEFAULTS[kind].unitsPerColumn) or 2), 1, 10, 2)
		raidMaxColumns = clampNumber(tonumber(cfg.maxColumns) or ((DEFAULTS[kind] and DEFAULTS[kind].maxColumns) or 1), 1, 10, 1)
		setAttr("unitsPerColumn", raidUnitsPerColumn)
		setAttr("maxColumns", raidMaxColumns)
	end

	local hasLiveRaidUnits = (kind == "raid") and (raidVisibleCount > 0)

	if header._eqolForceShow then
		setAttr("showParty", true)
		setAttr("showRaid", true)
		setAttr("showPlayer", true)
		setAttr("showSolo", true)
		if kind == "raid" and not hasLiveRaidUnits then raidVisibleCount = 1 end
	end

	local layoutPoint, layoutXOffset, layoutYOffset, layoutColumnSpacing, layoutColumnAnchorPoint
	if growth == "RIGHT" or growth == "LEFT" then
		local xOff = (growth == "LEFT") and -spacing or spacing
		local point = (growth == "LEFT") and "RIGHT" or "LEFT"
		layoutPoint = point
		layoutXOffset = roundToPixel(xOff, scale)
		layoutYOffset = 0
		if kind == "party" then
			layoutColumnSpacing = spacing
		else
			local columnSpacing = clampNumber(tonumber(cfg.columnSpacing) or spacing, 0, 40, spacing)
			layoutColumnSpacing = roundToPixel(columnSpacing, scale)
		end

		layoutColumnAnchorPoint = (kind == "raid" and not centerGrowthActive) and GF.GetRaidColumnAnchorPoint(growth, cfg.groupGrowth) or "TOP"
	else
		local yOff = (growth == "UP") and spacing or -spacing
		local point = (growth == "UP") and "BOTTOM" or "TOP"
		layoutPoint = point
		layoutXOffset = 0
		layoutYOffset = roundToPixel(yOff, scale)
		local columnSpacing = clampNumber(tonumber(cfg.columnSpacing) or spacing, 0, 40, spacing)
		layoutColumnSpacing = roundToPixel(columnSpacing, scale)
		layoutColumnAnchorPoint = (kind == "raid" and not centerGrowthActive) and GF.GetRaidColumnAnchorPoint(growth, cfg.groupGrowth) or "LEFT"
	end
	setAttr("template", "EQOLUFGroupUnitButtonTemplate")

	-- Pixel-perfect size: snap width/height to (even) screen pixels to avoid half-pixel centers -> text jitter.
	local w = clampNumber(tonumber(cfg.width) or 100, 40, 600, 100)
	local h = clampNumber(tonumber(cfg.height) or 24, 10, 200, 24)
	w = roundToEvenPixel(w, scale)
	h = roundToEvenPixel(h, scale)
	local renderW, renderH = w, h
	local renderXOffset, renderYOffset = layoutXOffset, layoutYOffset
	local renderColumnSpacing = layoutColumnSpacing

	if kind == "raid" then
		if useGroupHeaders then
			raidGroupSpecs = GF:BuildRaidGroupHeaderSpecs(cfg, sortMethod, useGroupedCustomSort)
			if header._eqolForceShow and not hasLiveRaidUnits then
				local soloSpec = raidGroupSpecs and raidGroupSpecs[1]
				if not soloSpec then soloSpec = {
					group = GF.GetFirstAllowedRaidGroup(cfg),
					sortMethod = (sortMethod == "NAME") and "NAME" or "INDEX",
				} end
				raidGroupSpecs = { soloSpec }
			end
		else
			local raidCount = raidVisibleCount
			local requiredColumns = raidMaxColumns or 1
			if raidCount > 0 then requiredColumns = max(requiredColumns, math.ceil(raidCount / max(1, raidUnitsPerColumn or 1))) end
			raidRuntimeMaxColumns = requiredColumns
			raidViewportScale = (
				GFH.GetRaidViewportScaleForColumns and GFH.GetRaidViewportScaleForColumns(growth, w, h, spacing, layoutColumnSpacing or spacing, raidMaxColumns or 1, raidRuntimeMaxColumns)
			) or 1
			raidViewportScale = GF.NormalizeRaidAutoFitScale(raidViewportScale)
			setAttr("maxColumns", raidRuntimeMaxColumns)
			renderW, renderH, renderXOffset, renderYOffset, renderColumnSpacing = GF.GetRaidAutoFitLayoutMetrics(
				growth,
				w,
				h,
				layoutXOffset,
				layoutYOffset,
				layoutColumnSpacing or spacing,
				raidViewportScale,
				scale,
				raidMaxColumns or 1,
				raidRuntimeMaxColumns
			)
		end
	end

	GF.PrepareSecureHeaderLayoutChange(
		header,
		GF.BuildSecureHeaderLayoutKey(
			layoutPoint,
			renderXOffset,
			renderYOffset,
			renderColumnSpacing,
			layoutColumnAnchorPoint,
			(kind == "raid" and not useGroupHeaders and raidRuntimeMaxColumns) or header:GetAttribute("maxColumns"),
			header:GetAttribute("unitsPerColumn")
		)
	)
	setAttr("point", layoutPoint)
	setAttr("xOffset", renderXOffset)
	setAttr("yOffset", renderYOffset)
	setAttr("columnSpacing", renderColumnSpacing)
	setAttr("columnAnchorPoint", layoutColumnAnchorPoint)

	local wStr = ("%.3f"):format(renderW)
	local hStr = ("%.3f"):format(renderH)
	local initConfigKey = wStr .. "x" .. hStr
	local initConfigFunction = header._eqolInitConfigFunction
	if header._eqolInitConfigKey ~= initConfigKey then
		initConfigFunction = string.format(
			[[
		self:ClearAllPoints()
		self:SetWidth(%s)
		self:SetHeight(%s)
		self:SetAttribute('*type1','target')
		self:SetAttribute('*type2','togglemenu')
		RegisterUnitWatch(self)
	]],
			wStr,
			hStr
		)
		header._eqolInitConfigKey = initConfigKey
		header._eqolInitConfigFunction = initConfigFunction
	end
	setAttr("initialConfigFunction", initConfigFunction)

	header._eqolFitScale = 1
	if header.SetScale then header:SetScale(1) end

	local headerChildLayoutKey = string.format(
		"%s|%.3f|%.3f|%.3f|%d",
		tostring(kind),
		tonumber(renderW) or 0,
		tonumber(renderH) or 0,
		(kind == "raid" and not useGroupHeaders) and (tonumber(raidViewportScale) or 1) or 1,
		useGroupHeaders and 1 or 0
	)
	local layoutChanged = GF.UpdateHeaderChildLayoutKey(header, headerChildLayoutKey)
	if not skipChildSync or layoutChanged then
		forEachChild(header, function(child) syncHeaderChild(child, kind, cfg, renderW, renderH, (kind == "raid" and not useGroupHeaders) and raidViewportScale or 1) end)
	end

	local anchor = GF.anchors and GF.anchors[kind]
	if anchor then
		GF.SetGroupAnchorPointFromCfg(anchor, kind, cfg)
		GF:UpdateAnchorSize(kind)
		header:ClearAllPoints()
		local p = getGrowthStartPoint(growth)
		if kind == "raid" and not useGroupHeaders and not centerGrowthActive then p = GF.GetRaidLayoutStartPoint(growth, cfg.groupGrowth) end
		local rp = p
		if centerGrowthActive then rp = GF.GetCenterGrowthRelativePoint(growth) end
		local anchorOffsetX, anchorOffsetY = 0, 0
		if centerGrowthActive then
			if kind == "party" then
				anchorOffsetX, anchorOffsetY = GF.ComputePartyCenterGrowthAnchorOffset(cfg, growth, scale)
			elseif kind == "raid" and not useGroupHeaders then
				local perLineCount = min(max(raidVisibleCount, 0), raidUnitsPerColumn or 5)
				local runtimeColumns = max(1, math.ceil(max(raidVisibleCount, 0) / max(1, raidUnitsPerColumn or 1)))
				local totalSpan = 0
				if perLineCount > 0 then
					if growth == "RIGHT" or growth == "LEFT" then
						totalSpan = renderW * perLineCount + spacing * max(0, perLineCount - 1)
					else
						totalSpan = renderH * perLineCount + spacing * max(0, perLineCount - 1)
					end
				end
				anchorOffsetX, anchorOffsetY = GF.ComputeCenterGrowthOffsetFromSpan(growth, totalSpan, scale)
				local crossGrowth
				local viewportCrossSpan
				local contentCrossSpan
				if growth == "RIGHT" or growth == "LEFT" then
					crossGrowth = "DOWN"
					viewportCrossSpan = h * (raidMaxColumns or 1) + (layoutColumnSpacing or spacing) * max(0, (raidMaxColumns or 1) - 1)
					contentCrossSpan = renderH * runtimeColumns + renderColumnSpacing * max(0, runtimeColumns - 1)
				else
					crossGrowth = "RIGHT"
					viewportCrossSpan = w * (raidMaxColumns or 1) + (layoutColumnSpacing or spacing) * max(0, (raidMaxColumns or 1) - 1)
					contentCrossSpan = renderW * runtimeColumns + renderColumnSpacing * max(0, runtimeColumns - 1)
				end
				local crossOffsetX, crossOffsetY = GF.ComputeViewportCenteringOffset(crossGrowth, viewportCrossSpan, contentCrossSpan, scale)
				anchorOffsetX = anchorOffsetX + crossOffsetX
				anchorOffsetY = anchorOffsetY + crossOffsetY
			end
		end
		if Pixel and Pixel.SetPoint then
			Pixel.SetPoint(header, p, anchor, rp, anchorOffsetX, anchorOffsetY)
		else
			header:SetPoint(p, anchor, rp, anchorOffsetX, anchorOffsetY)
		end
		if kind == "party" then GF:ApplyPartyGroupBorder(cfg, header, anchor, growth, scale, spacing, w, h, p, rp, anchorOffsetX, anchorOffsetY) end
	else
		GF.SetGroupAnchorPointFromCfg(header, kind, cfg)
		if kind == "party" then GF.HideGroupBorder("party") end
	end

	local forceHide = header._eqolForceHide
	local forceShow = header._eqolForceShow
	if kind == "raid" then
		header._eqolSpecialHide = useGroupHeaders == true
	elseif isSplitRoleKind(kind) then
		header._eqolSpecialHide = raidFramesEnabled ~= true or splitRoleViewerAllowed ~= true
	else
		header._eqolSpecialHide = nil
	end
	applyVisibility(header, kind, cfg)
	if kind == "party" then
		local border = GF.groupBorders and GF.groupBorders.party
		if border and RegisterStateDriver then
			if UnregisterStateDriver then UnregisterStateDriver(border, "visibility") end
			RegisterStateDriver(border, "visibility", header._eqolVisibilityCond or "hide")
		end
	end

	if kind == "raid" then
		if useGroupHeaders then
			local isHorizontal = (growth == "RIGHT" or growth == "LEFT")
			local unitsPer = raidUnitsPerColumn or 5
			local groupSpecs = raidGroupSpecs or GF:BuildRaidGroupHeaderSpecs(cfg, sortMethod, useGroupedCustomSort)
			local groupsPerRow = GF.NormalizeRaidGroupsPerRow(cfg.groupsPerRow, DEFAULTS.raid and DEFAULTS.raid.groupsPerRow)
			local perHeaderW, perHeaderH
			if isHorizontal then
				perHeaderW = w * unitsPer + spacing * max(0, unitsPer - 1)
				perHeaderH = h
			else
				perHeaderW = w
				perHeaderH = h * unitsPer + spacing * max(0, unitsPer - 1)
			end
			perHeaderW = roundToEvenPixel(perHeaderW, scale)
			perHeaderH = roundToEvenPixel(perHeaderH, scale)
			local runtimeGroupCount = #groupSpecs
			local runtimeGroupsPerLine = min(runtimeGroupCount, groupsPerRow)
			local runtimeGroupLines = max(1, math.ceil(runtimeGroupCount / groupsPerRow))
			local viewportGroupCount = max(1, floor((tonumber(raidMaxColumns) or 1) + 0.5))
			if groupsPerRow < viewportGroupCount then viewportGroupCount = groupsPerRow end
			local groupViewportScale = (
				GFH.GetRaidViewportScaleForGroups
				and GFH.GetRaidViewportScaleForGroups(cfg.groupGrowth, perHeaderW, perHeaderH, layoutColumnSpacing or spacing, viewportGroupCount, runtimeGroupsPerLine)
			) or 1
			groupViewportScale = GF.NormalizeRaidAutoFitScale(groupViewportScale)
			local groupRenderW, groupRenderH, groupXOffset, groupYOffset, groupRenderSpacing = GF.GetRaidAutoFitLayoutMetrics(
				growth,
				w,
				h,
				layoutXOffset,
				layoutYOffset,
				layoutColumnSpacing or spacing,
				groupViewportScale,
				scale,
				viewportGroupCount,
				runtimeGroupsPerLine
			)
			local renderedPerHeaderW, renderedPerHeaderH
			if isHorizontal then
				renderedPerHeaderW = roundToEvenPixel(groupRenderW * unitsPer + spacing * max(0, unitsPer - 1), scale)
				renderedPerHeaderH = groupRenderH
			else
				renderedPerHeaderW = groupRenderW
				renderedPerHeaderH = roundToEvenPixel(groupRenderH * unitsPer + spacing * max(0, unitsPer - 1), scale)
			end
			local groupCenterOffsetX, groupCenterOffsetY = 0, 0
			if centerGrowthActive and runtimeGroupCount > 0 then
				local totalSpan
				if isHorizontal then
					if cfg.groupGrowth == "LEFT" or cfg.groupGrowth == "RIGHT" then
						totalSpan = renderedPerHeaderW * runtimeGroupsPerLine + groupRenderSpacing * max(0, runtimeGroupsPerLine - 1)
					else
						totalSpan = renderedPerHeaderW
					end
				else
					if cfg.groupGrowth == "UP" or cfg.groupGrowth == "DOWN" then
						totalSpan = renderedPerHeaderH * runtimeGroupsPerLine + groupRenderSpacing * max(0, runtimeGroupsPerLine - 1)
					else
						totalSpan = renderedPerHeaderH
					end
				end
				groupCenterOffsetX, groupCenterOffsetY = GF.ComputeCenterGrowthOffsetFromSpan(growth, totalSpan, scale)
				local viewportCrossSpan
				local contentCrossSpan
				if isHorizontal then
					viewportCrossSpan = perHeaderH * viewportGroupCount + (layoutColumnSpacing or spacing) * max(0, viewportGroupCount - 1)
					contentCrossSpan = renderedPerHeaderH * runtimeGroupLines + groupRenderSpacing * max(0, runtimeGroupLines - 1)
				else
					viewportCrossSpan = perHeaderW * viewportGroupCount + (layoutColumnSpacing or spacing) * max(0, viewportGroupCount - 1)
					contentCrossSpan = renderedPerHeaderW * runtimeGroupLines + groupRenderSpacing * max(0, runtimeGroupLines - 1)
				end
				local crossOffsetX, crossOffsetY = GF.ComputeViewportCenteringOffset(cfg.groupGrowth, viewportCrossSpan, contentCrossSpan, scale)
				groupCenterOffsetX = groupCenterOffsetX + crossOffsetX
				groupCenterOffsetY = groupCenterOffsetY + crossOffsetY
			end
			local groupInitConfigFunction = string.format(
				[[
		self:ClearAllPoints()
		self:SetWidth(%s)
		self:SetHeight(%s)
		self:SetAttribute('*type1','target')
		self:SetAttribute('*type2','togglemenu')
		RegisterUnitWatch(self)
	]],
				("%.3f"):format(groupRenderW),
				("%.3f"):format(groupRenderH)
			)
			local layout = {
				scale = scale,
				w = groupRenderW,
				h = groupRenderH,
				point = layoutPoint,
				xOffset = groupXOffset,
				yOffset = groupYOffset,
				columnSpacing = groupRenderSpacing,
				columnAnchorPoint = layoutColumnAnchorPoint or "LEFT",
				unitsPerColumn = unitsPer,
				perHeaderW = renderedPerHeaderW,
				perHeaderH = renderedPerHeaderH,
				minWidth = isHorizontal and renderedPerHeaderW or groupRenderW,
				minHeight = isHorizontal and groupRenderH or renderedPerHeaderH,
				fitScale = groupViewportScale,
				startPoint = getGrowthStartPoint(growth),
				growth = growth,
				groupGrowth = cfg.groupGrowth,
				groupsPerRow = groupsPerRow,
				groupScale = groupViewportScale,
				centerRelativePoint = centerGrowthActive and GF.GetCenterGrowthRelativePoint(growth) or nil,
				centerOffsetX = groupCenterOffsetX,
				centerOffsetY = groupCenterOffsetY,
				initConfigFunction = groupInitConfigFunction,
			}

			applyRaidGroupHeaders(cfg, layout, groupSpecs, forceShow, forceHide, runtimeGroupCount, options)
		elseif GF._raidGroupHeaders then
			local layout = {
				scale = scale,
				w = w,
				h = h,
				point = layoutPoint,
				xOffset = layoutXOffset,
				yOffset = layoutYOffset,
				columnSpacing = layoutColumnSpacing or spacing,
				columnAnchorPoint = layoutColumnAnchorPoint or "LEFT",
				unitsPerColumn = raidUnitsPerColumn or 5,
				perHeaderW = w,
				perHeaderH = h,
				minWidth = w,
				minHeight = h,
				startPoint = getGrowthStartPoint(growth),
				growth = growth,
				groupGrowth = cfg.groupGrowth,
				initConfigFunction = initConfigFunction,
			}
			applyRaidGroupHeaders(cfg, layout, nil, forceShow, forceHide, 0, options)
		end
	end
	if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end

	if header.IsShown and header:IsShown() and header.Show then header:Show() end

	if header.IsShown and header:IsShown() then
		nudgeHeaderLayout(header)
	else
		header._eqolPendingLayout = true
	end

	if kind == "raid" then GF:RefreshGroupIndicators() end
end

function GF:EnsureHeaders()
	if not isFeatureEnabled() then return end
	if GF.headers.party and GF.headers.raid and GF.headers.mt and GF.headers.ma and GF.anchors.party and GF.anchors.raid and GF.anchors.mt and GF.anchors.ma then return end

	local parent = _G.PetBattleFrameHider or UIParent

	if not GF.anchors.party then ensureAnchor("party", parent) end
	if not GF.anchors.raid then ensureAnchor("raid", parent) end
	if not GF.anchors.mt then ensureAnchor("mt", parent) end
	if not GF.anchors.ma then ensureAnchor("ma", parent) end

	if not GF.headers.party then
		GF.headers.party = CreateFrame("Frame", "EQOLUFPartyHeader", parent, "SecureGroupHeaderTemplate")
		GF.headers.party._eqolKind = "party"
		if GF.headers.party.SetClampedToScreen then GF.headers.party:SetClampedToScreen(true) end
		GF.headers.party:Hide()
	end

	if not GF.headers.raid then
		GF.headers.raid = CreateFrame("Frame", "EQOLUFRaidHeader", parent, "SecureGroupHeaderTemplate")
		GF.headers.raid._eqolKind = "raid"
		if GF.headers.raid.SetClampedToScreen then GF.headers.raid:SetClampedToScreen(true) end
		GF.headers.raid:Hide()
	end

	if not GF.headers.mt then
		GF.headers.mt = CreateFrame("Frame", "EQOLUFMTHeader", parent, "SecureGroupHeaderTemplate")
		GF.headers.mt._eqolKind = "mt"
		if GF.headers.mt.SetClampedToScreen then GF.headers.mt:SetClampedToScreen(true) end
		GF.headers.mt:Hide()
	end

	if not GF.headers.ma then
		GF.headers.ma = CreateFrame("Frame", "EQOLUFMAHeader", parent, "SecureGroupHeaderTemplate")
		GF.headers.ma._eqolKind = "ma"
		if GF.headers.ma.SetClampedToScreen then GF.headers.ma:SetClampedToScreen(true) end
		GF.headers.ma:Hide()
	end

	for kind, header in pairs(GF.headers) do
		if header and not header._eqolLayoutHooked then
			header._eqolLayoutHooked = true
			header:HookScript("OnShow", function(self)
				if self._eqolPendingLayout then nudgeHeaderLayout(self) end
			end)
		end
		local a = GF.anchors and GF.anchors[kind]
		if header and a then
			header:ClearAllPoints()
			local cfg = getCfg(kind)
			local p = cfg and (cfg.point or "CENTER") or "CENTER"
			header:SetPoint(p, a, p, 0, 0)
		end
	end

	GF:ApplyHeaderAttributes("party")
	GF:ApplyHeaderAttributes("raid")
	GF:ApplyHeaderAttributes("mt")
	GF:ApplyHeaderAttributes("ma")
end

function GF:EnableFeature()
	registerFeatureEvents(GF._eventFrame)
	GF:EnsureHeaders()
	GF.FullRefresh()
	GF:DisableBlizzardFrames()
	GF:EnsureEditMode()
end

function GF:DisableFeature()
	if InCombatLockdown and InCombatLockdown() then
		GF._pendingDisable = true
		return
	end
	cancelQueuedGroupIndicatorRefresh()
	GF._pendingDisable = nil
	GF:CancelPostRosterRefreshTicker()
	GF:CancelPostEnterWorldRefreshTicker()
	GF:CancelConnectionRefreshTicker()
	GF:CancelAllConnectionRecheckTimers()
	unregisterFeatureEvents(GF._eventFrame)

	if EditMode and EditMode.UnregisterFrame and type(EDITMODE_IDS) == "table" then
		for _, id in pairs(EDITMODE_IDS) do
			pcall(EditMode.UnregisterFrame, EditMode, id)
		end
	end
	GF._editModeRegistered = nil

	if GF.headers then
		for _, header in pairs(GF.headers) do
			if UnregisterStateDriver then UnregisterStateDriver(header, "visibility") end
			if RegisterStateDriver then RegisterStateDriver(header, "visibility", "hide") end
			if header.Hide then header:Hide() end
		end
	end
	if GF.anchors then
		for _, anchor in pairs(GF.anchors) do
			if anchor.Hide then anchor:Hide() end
		end
	end
	if GF.groupBorders then
		for kind in pairs(GF.groupBorders) do
			GF.HideGroupBorder(kind)
		end
	end
end

function GF.Enable(self, kind)
	if type(self) == "string" and kind == nil then kind = self end
	if type(kind) ~= "string" then return end
	local cfg = getCfg(kind)
	if not cfg then return end
	cfg.enabled = true
	if UF and UF.SetRuntimeConsumerActive then UF.SetRuntimeConsumerActive("group", kind, true) end
	if UF and UF.Profiles and UF.Profiles.Trace then UF.Profiles.Trace("GF_ENABLE_KIND", kind) end
	GF:EnsureHeaders()
	GF:ApplyHeaderAttributes(kind)
	GF:EnableFeature()
	GF:DisableBlizzardFrames()
end

function GF.Disable(self, kind)
	if type(self) == "string" and kind == nil then kind = self end
	if type(kind) ~= "string" then return end
	local cfg = getCfg(kind)
	if not cfg then return end
	cfg.enabled = false
	if UF and UF.SetRuntimeConsumerActive then UF.SetRuntimeConsumerActive("group", kind, false) end
	if UF and UF.Profiles and UF.Profiles.Trace then UF.Profiles.Trace("GF_DISABLE_KIND", kind) end
	GF:EnsureHeaders()
	local header = GF.headers and GF.headers[kind]
	if header then
		header._eqolForceShow = nil
		header._eqolForceHide = nil
	end
	if GF._previewActive then GF._previewActive[kind] = nil end
	GF:ShowPreviewFrames(kind, false)
	GF:ApplyHeaderAttributes(kind)
	if not isFeatureEnabled() then GF:DisableFeature() end
end

function GF:DidRosterStateChange()
	local state = self._rosterState
	if not state then
		state = { mode = nil, signature = nil }
		self._rosterState = state
	end

	local mode
	local groupCount = (GetNumGroupMembers and GetNumGroupMembers()) or 0
	local subgroupCount = (GetNumSubgroupMembers and GetNumSubgroupMembers()) or 0
	if IsInRaid and IsInRaid() then
		mode = "raid"
	elseif IsInGroup and IsInGroup() then
		mode = "party"
	else
		mode = "solo"
	end

	local signatureParts = {
		mode,
		tostring(groupCount),
		tostring(subgroupCount),
	}

	if mode == "party" or mode == "solo" then
		local partyCfg = getCfg("party")
		local partySortState = GF.BuildPartyRuntimeSortState(partyCfg)
		signatureParts[#signatureParts + 1] = tostring(partySortState and partySortState.sortMethod or "")
		signatureParts[#signatureParts + 1] = tostring(partySortState and partySortState.sortDir or "")
		signatureParts[#signatureParts + 1] = tostring(partySortState and partySortState.nameList or "")
	else
		local cfg = getCfg("raid")
		local raidSortState = GF.BuildRaidRuntimeSortState(cfg)
		local configuredSortMethod = raidSortState and raidSortState.configuredSortMethod or resolveSortMethod(cfg)
		local custom = cfg and GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
		local useGroupedCustomSort = raidSortState and raidSortState.useGroupedCustom and (custom and custom.enabled == true)
		local useGroupHeaders = GF:IsRaidGroupedLayout(cfg) and (configuredSortMethod ~= "NAMELIST" or useGroupedCustomSort)
		signatureParts[#signatureParts + 1] = tostring(useGroupHeaders)
		signatureParts[#signatureParts + 1] = tostring(raidSortState and raidSortState.sortMethod or "")
		signatureParts[#signatureParts + 1] = tostring(raidSortState and raidSortState.sortDir or "")
		signatureParts[#signatureParts + 1] = tostring(raidSortState and raidSortState.nameList or "")

		if useGroupHeaders then
			local specs = raidSortState and raidSortState.groupedSpecs
			if not specs then specs = GF:BuildRaidGroupHeaderSpecs(cfg, configuredSortMethod, useGroupedCustomSort) end
			for _, spec in ipairs(specs or {}) do
				signatureParts[#signatureParts + 1] = string.format("%s:%s:%s", tostring(spec and spec.group or ""), tostring(spec and spec.sortMethod or ""), tostring(spec and spec.nameList or ""))
			end
		end
	end

	local signature = table.concat(signatureParts, "\031")
	local modeChanged = state.mode ~= mode
	local changed = state.signature ~= signature
	state.mode = mode
	state.signature = signature

	return changed, modeChanged
end

function GF:RefreshChangedUnitButtons()
	local updated = 0
	local seen = self._refreshChangedUnitSeen
	if type(seen) ~= "table" then
		seen = {}
		self._refreshChangedUnitSeen = seen
	else
		wipe(seen)
	end

	local function syncChild(child)
		if not child or seen[child] then return end
		seen[child] = true

		local st = getState(child)
		if not st then return end
		local useSecureUnitAttribute = child._eqolUseSecureUnitAttribute == true

		local unit = getUnit(child)
		if unit == nil or unit == "" then
			if child.unit then
				if useSecureUnitAttribute then return end
				GF:UnitButton_ClearUnit(child)
				GF:UpdateAll(child)
				updated = updated + 1
			end
			return
		end

		local guid = UnitGUID and UnitGUID(unit) or nil
		if issecretvalue and issecretvalue(guid) then guid = nil end
		local cachedGuid = st._guid
		if issecretvalue and issecretvalue(cachedGuid) then
			cachedGuid = nil
			st._guid = nil
		end
		if cachedGuid == guid and st._unitToken == unit and child.unit == unit then
			local cfg = child._eqolCfg or getCfg(child._eqolGroupKind or "party")
			if st._classR == nil and GF:NeedsClassColor(child, st, cfg) then
				GF:CacheUnitStatic(child)
				GF:EnsureUnitClassColor(child, st, unit)
				GF:UpdateHealthStyle(child)
				GF:UpdateName(child)
				GF:UpdateLevel(child)
				updated = updated + 1
			end
			return
		end

		if child.unit ~= unit then
			if useSecureUnitAttribute then return end
			GF:UnitButton_SetUnit(child, unit)
			updated = updated + 1
			return
		end

		st._auraCache = nil
		st._auraCacheByKey = nil
		st._auraKindById = nil
		st._auraChanged = nil
		st._auraQueryMax = nil
		clearDispelAuraState(st)
		GF:CacheUnitStatic(child)
		GF:UnitButton_RegisterUnitEvents(child, unit)
		GF:UpdatePrivateAuras(child)
		GF:UpdateAll(child)
		updated = updated + 1
	end

	for _, header in pairs(self.headers or {}) do
		forEachChild(header, syncChild)
	end
	if self._raidGroupHeaders then
		for _, header in ipairs(self._raidGroupHeaders) do
			if header then forEachChild(header, syncChild) end
		end
	end

	wipe(seen)
	return updated
end

function GF:RefreshSplitRoleHeadersForViewerRoleChange()
	if InCombatLockdown and InCombatLockdown() then
		GF:MarkPendingHeaderRefresh("mt")
		GF:MarkPendingHeaderRefresh("ma")
		return
	end
	local options = GF._lightHeaderRefreshOptions
	GF:ApplyHeaderAttributes("mt", options)
	GF:ApplyHeaderAttributes("ma", options)
	GF:RefreshChangedUnitButtons()
end

function GF.FullRefresh(kind)
	if not isFeatureEnabled() then return 0 end
	GF:EnsureHeaders()
	if kind then
		GF:ApplyHeaderAttributes(kind)
	else
		GF:ApplyHeaderAttributes("party")
		GF:ApplyHeaderAttributes("raid")
		GF:ApplyHeaderAttributes("mt")
		GF:ApplyHeaderAttributes("ma")
	end
	return GF:RefreshChangedUnitButtons()
end

function GF.Refresh(kind)
	if not isFeatureEnabled() then return 0 end
	GF:EnsureHeaders()
	local options = GF._lightHeaderRefreshOptions
	if kind then
		GF:ApplyHeaderAttributes(kind, options)
	else
		GF:ApplyHeaderAttributes("party", options)
		GF:ApplyHeaderAttributes("raid", options)
		GF:ApplyHeaderAttributes("mt", options)
		GF:ApplyHeaderAttributes("ma", options)
	end
	return GF:RefreshChangedUnitButtons()
end

local EDITMODE_IDS = {
	party = "EQOL_UF_GROUP_PARTY",
	raid = "EQOL_UF_GROUP_RAID",
	mt = "EQOL_UF_GROUP_MT",
	ma = "EQOL_UF_GROUP_MA",
}

GF._groupCopySourceGroup = "group"
GF._groupCopySourceUnit = "unit"
GF._groupCopyDialogKey = "EQOL_UF_GROUP_COPY_SETTINGS"

GF._groupCopySourceLabels = {
	party = PARTY or "Party",
	raid = RAID or "Raid",
	mt = MAINTANK or "Main Tank",
	ma = MAINASSIST or "Main Assist",
}

GF._groupCopyUnitSourceLabels = {
	player = PLAYER or "Player",
	target = TARGET or "Target",
	targettarget = L["UFToTFrame"] or "Target of Target",
	pet = PET or "Pet",
	focus = FOCUS or "Focus",
	boss = BOSS or "Boss",
}

GF._groupCopySectionOrder = {
	"frame",
	"layout",
	"border",
	"hoverHighlight",
	"aggroHighlight",
	"targetHighlight",
	"portrait",
	"text",
	"dataBar",
	"health",
	"incomingheal",
	"absorb",
	"healabsorb",
	"level",
	"statustext",
	"rangefade",
	"dispeltint",
	"groupicons",
	"raidmarker",
	"statusicons",
	"roleicons",
	"power",
	"buffs",
	"debuffs",
	"externals",
	"healerBuffPlacement",
	"privateAuras",
	"party",
	"raid",
}

GF._groupCopySectionLabels = {
	frame = L["Frame"] or "Frame",
	layout = L["Layout"] or "Layout",
	border = EMBLEM_BORDER,
	hoverHighlight = L["Hover highlight"] or "Hover highlight",
	aggroHighlight = L["Aggro highlight"] or "Aggro highlight",
	targetHighlight = L["Target highlight"] or "Target highlight",
	portrait = "Portrait",
	text = L["Name"] or "Name",
	dataBar = L["UFDataBar"] or "Data bar",
	health = L["Health"] or "Health",
	incomingheal = L["Incoming heals"] or "Incoming heals",
	absorb = L["Absorb"] or "Absorb",
	healabsorb = L["Heal absorb"] or "Heal absorb",
	level = L["Level"] or "Level",
	statustext = L["Status text"] or "Status text",
	rangefade = L["UFRangeFade"] or "Range fade",
	dispeltint = L["UFDispelIndicator"] or "Dispel indicator",
	groupicons = L["Group icons"] or "Group icons",
	raidmarker = L["Raid marker"] or "Raid marker",
	statusicons = "Status icons",
	roleicons = L["Role icons"] or "Role icons",
	power = L["Power"] or "Power",
	buffs = L["Buffs"] or "Buffs",
	debuffs = "Debuffs",
	externals = "Externals",
	healerBuffPlacement = "Healer buff placement",
	privateAuras = L["Private Auras"] or "Private Auras",
	party = PARTY or "Party",
	raid = RAID or "Raid",
}

GF._groupCopySectionRules = {
	frame = {
		{ "width" },
		{ "height" },
		{ "powerHeight" },
		{ "hideInClientScene" },
		{ "tooltip" },
		{ "point" },
		{ "relativePoint" },
		{ "relativeTo" },
		{ "x" },
		{ "y" },
	},
	layout = {
		{ "spacing" },
		{ "growth" },
		{ "groupGrowth" },
		{ "groupsPerRow" },
		{ "barTexture" },
		{ "unitsPerColumn" },
		{ "maxColumns" },
		{ "columnSpacing" },
	},
	border = {
		{ "border" },
	},
	hoverHighlight = {
		{ "highlightHover" },
	},
	aggroHighlight = {
		{ "highlightAggro" },
	},
	targetHighlight = {
		{ "highlightTarget" },
	},
	portrait = {
		{ "portrait" },
	},
	text = {
		{ "text" },
		{ "status", "nameColorMode" },
		{ "status", "nameColor" },
		{ "status", "nameStrata" },
		{ "status", "nameFrameLevelOffset" },
	},
	dataBar = {
		{ "dataBar" },
	},
	health = {
		{ "health" },
	},
	incomingheal = {
		{ "health", "incomingHealEnabled" },
		{ "health", "showSampleIncomingHeal" },
		{ "health", "incomingHealTexture" },
		{ "health", "incomingHealColor" },
	},
	absorb = {
		{ "health", "absorbEnabled" },
		{ "health", "showSampleAbsorb" },
		{ "health", "absorbTexture" },
		{ "health", "absorbReverseFill" },
		{ "health", "absorbDontOverflowHealthBar" },
		{ "health", "absorbOverlayHeight" },
		{ "health", "absorbOverlayAnchorTop" },
		{ "health", "absorbUseCustomColor" },
		{ "health", "absorbColor" },
		{ "health", "useAbsorbGlow" },
	},
	healabsorb = {
		{ "health", "healAbsorbEnabled" },
		{ "health", "showSampleHealAbsorb" },
		{ "health", "healAbsorbTexture" },
		{ "health", "healAbsorbReverseFill" },
		{ "health", "healAbsorbDontOverflowHealthBar" },
		{ "health", "healAbsorbOverlayHeight" },
		{ "health", "healAbsorbOverlayAnchorTop" },
		{ "health", "healAbsorbUseCustomColor" },
		{ "health", "healAbsorbColor" },
	},
	level = {
		{ "status", "levelEnabled" },
		{ "status", "hideLevelAtMax" },
		{ "status", "levelColorMode" },
		{ "status", "levelColor" },
		{ "status", "levelFontSize" },
		{ "status", "levelFont" },
		{ "status", "levelFontOutline" },
		{ "status", "levelAnchor" },
		{ "status", "levelStrata" },
		{ "status", "levelFrameLevelOffset" },
		{ "status", "levelOffset" },
	},
	statustext = {
		{ "status", "unitStatus" },
		{ "status", "groupNumber" },
	},
	rangefade = {
		{ "status", "rangeFade" },
	},
	dispeltint = {
		{ "status", "dispelTint" },
	},
	groupicons = {
		{ "status", "leaderIcon" },
		{ "status", "raidAssignmentIcon" },
		{ "status", "assistIcon" },
	},
	raidmarker = {
		{ "status", "raidIcon" },
	},
	statusicons = {
		{ "status", "readyCheckIcon" },
		{ "status", "summonIcon" },
		{ "status", "resurrectIcon" },
		{ "status", "phaseIcon" },
	},
	roleicons = {
		{ "roleIcon" },
	},
	power = {
		{ "powerHeight" },
		{ "power" },
	},
	buffs = {
		{ "auras", "buff" },
	},
	debuffs = {
		{ "auras", "debuff" },
	},
	externals = {
		{ "auras", "externals" },
	},
	healerBuffPlacement = {
		{ "healerBuffPlacement" },
	},
	privateAuras = {
		{ "privateAuras" },
	},
	party = {
		{ "showPlayer" },
		{ "showSolo" },
		{ "sortMethod" },
		{ "sortDir" },
		{ "customSort" },
	},
	raid = {
		{ "viewerRoleFilter" },
		{ "unitsPerColumn" },
		{ "maxColumns" },
		{ "groupsPerRow" },
		{ "columnSpacing" },
		{ "groupBy" },
		{ "groupingOrder" },
		{ "groupFilter" },
		{ "sortMethod" },
		{ "sortDir" },
		{ "customSort" },
		{ "groupIndicator" },
	},
}

GF._groupCopyUnitSections = {
	frame = true,
	border = true,
	text = true,
	portrait = true,
	health = true,
	incomingheal = true,
	absorb = true,
	healabsorb = true,
	level = true,
	statustext = true,
	raidmarker = true,
	power = true,
	buffs = true,
	debuffs = true,
	privateAuras = true,
}

function GF._groupCopyCloneValue(value)
	if type(value) ~= "table" then return value end
	if addon.functions and addon.functions.copyTable then return addon.functions.copyTable(value) end
	if CopyTable then return CopyTable(value) end
	local out = {}
	for key, child in pairs(value) do
		out[key] = GF._groupCopyCloneValue(child)
	end
	return out
end

function GF._groupCopyGetPathValue(root, path)
	if type(root) ~= "table" or type(path) ~= "table" then return nil, false end
	local cur = root
	for i = 1, #path do
		if type(cur) ~= "table" then return nil, false end
		cur = cur[path[i]]
		if cur == nil then return nil, false end
	end
	return cur, true
end

function GF._groupCopyClearPathValue(root, path)
	if type(root) ~= "table" or type(path) ~= "table" or #path == 0 then return end
	if #path == 1 then
		root[path[1]] = nil
		return
	end
	local cur = root
	local trail = {}
	for i = 1, #path - 1 do
		local key = path[i]
		local nxt = cur[key]
		if type(nxt) ~= "table" then return end
		trail[#trail + 1] = { parent = cur, key = key }
		cur = nxt
	end
	cur[path[#path]] = nil
	for i = #trail, 1, -1 do
		local node = trail[i]
		local child = node.parent[node.key]
		if type(child) == "table" and not next(child) then
			node.parent[node.key] = nil
		else
			break
		end
	end
end

function GF._groupCopySetPathValue(root, path, value)
	if type(root) ~= "table" or type(path) ~= "table" or #path == 0 then return end
	if value == nil then
		GF._groupCopyClearPathValue(root, path)
		return
	end
	local cur = root
	for i = 1, #path - 1 do
		local key = path[i]
		if type(cur[key]) ~= "table" then cur[key] = {} end
		cur = cur[key]
	end
	cur[path[#path]] = value
end

function GF._groupCopyPath(src, dest, path)
	local value, exists = GF._groupCopyGetPathValue(src, path)
	if exists then
		GF._groupCopySetPathValue(dest, path, GF._groupCopyCloneValue(value))
	else
		GF._groupCopyClearPathValue(dest, path)
	end
end

function GF._groupCopyPathWithDefaults(sourceKind, src, dest, path)
	local value, exists = GF._groupCopyGetPathValue(src, path)
	if not exists then
		local def = DEFAULTS[sourceKind]
		if type(def) == "table" then
			value, exists = GF._groupCopyGetPathValue(def, path)
		end
	end
	if exists then GF._groupCopySetPathValue(dest, path, GF._groupCopyCloneValue(value)) end
end

function GF._groupCopyWipeTarget(t)
	if type(t) ~= "table" then return end
	if wipe then
		wipe(t)
		return
	end
	for key in pairs(t) do
		t[key] = nil
	end
end

function GF._isGroupCopyKind(kind) return kind == "party" or kind == "raid" or kind == "mt" or kind == "ma" end

function GF._buildGroupCopySectionSetForGroupKind(kind)
	local set = {
		frame = true,
		layout = true,
		border = true,
		hoverHighlight = true,
		aggroHighlight = true,
		targetHighlight = true,
		portrait = kind ~= "raid",
		text = true,
		health = true,
		incomingheal = true,
		absorb = true,
		healabsorb = true,
		level = true,
		statustext = true,
		rangefade = true,
		dispeltint = true,
		groupicons = true,
		raidmarker = true,
		statusicons = true,
		roleicons = true,
		power = true,
		buffs = true,
		debuffs = true,
		externals = true,
		privateAuras = true,
	}
	if kind == "party" then
		set.party = true
		set.healerBuffPlacement = true
	elseif kind == "raid" then
		set.raid = true
		set.healerBuffPlacement = true
	elseif kind == "mt" or kind == "ma" then
		set.raid = true
	end
	return set
end

function GF._buildGroupCopySectionSetForUnitSource()
	local set = {}
	for sectionId in pairs(GF._groupCopyUnitSections) do
		set[sectionId] = true
	end
	return set
end

function GF._buildGroupCopySourceOptions(targetKind)
	local options = {}
	for _, sourceKind in ipairs({ "party", "raid", "mt", "ma" }) do
		if sourceKind ~= targetKind then
			options[#options + 1] = {
				sourceType = GF._groupCopySourceGroup,
				sourceKey = sourceKind,
				label = GF._groupCopySourceLabels[sourceKind] or sourceKind,
			}
		end
	end
	for _, sourceUnit in ipairs({ "player", "target", "targettarget", "focus", "pet", "boss" }) do
		options[#options + 1] = {
			sourceType = GF._groupCopySourceUnit,
			sourceKey = sourceUnit,
			label = GF._groupCopyUnitSourceLabels[sourceUnit] or sourceUnit,
		}
	end
	return options
end

function GF._buildGroupCopySectionOptions(source, targetKind)
	local options = {}
	if type(source) ~= "table" or not GF._isGroupCopyKind(targetKind) then return options end
	local sourceSet
	if source.sourceType == GF._groupCopySourceGroup and GF._isGroupCopyKind(source.sourceKey) then
		sourceSet = GF._buildGroupCopySectionSetForGroupKind(source.sourceKey)
	elseif source.sourceType == GF._groupCopySourceUnit then
		sourceSet = GF._buildGroupCopySectionSetForUnitSource()
	else
		return options
	end
	local targetSet = GF._buildGroupCopySectionSetForGroupKind(targetKind)
	for _, sectionId in ipairs(GF._groupCopySectionOrder) do
		if sourceSet[sectionId] and targetSet[sectionId] then options[#options + 1] = { value = sectionId, label = GF._groupCopySectionLabels[sectionId] or sectionId } end
	end
	return options
end

function GF._getGroupCopyTargetLabel(kind) return GF._groupCopySourceLabels[kind] or kind end

function GF._getGroupCopySourceLabel(source)
	if type(source) ~= "table" then return "" end
	if source.label and source.label ~= "" then return source.label end
	if source.sourceType == GF._groupCopySourceGroup then return GF._groupCopySourceLabels[source.sourceKey] or source.sourceKey end
	if source.sourceType == GF._groupCopySourceUnit then return GF._groupCopyUnitSourceLabels[source.sourceKey] or source.sourceKey end
	return tostring(source.sourceKey or "")
end

function GF._ensureGroupCopySectionSelection(payload)
	if type(payload) ~= "table" then return end
	payload.sectionOptions = type(payload.sectionOptions) == "table" and payload.sectionOptions or {}
	payload.sectionSelection = type(payload.sectionSelection) == "table" and payload.sectionSelection or {}
	for _, option in ipairs(payload.sectionOptions) do
		if payload.sectionSelection[option.value] == nil then payload.sectionSelection[option.value] = true end
	end
end

function GF._getSelectedGroupCopySections(payload)
	local selected = {}
	if type(payload) ~= "table" then return selected end
	for _, option in ipairs(payload.sectionOptions or {}) do
		if payload.sectionSelection and payload.sectionSelection[option.value] == true then selected[#selected + 1] = option.value end
	end
	return selected
end

function GF._resolveGroupCopySectionSelection(sectionOptions, selectedSections)
	local resolved = {}
	local selectedMap = nil
	if type(selectedSections) == "table" then
		selectedMap = {}
		for _, sectionId in ipairs(selectedSections) do
			if type(sectionId) == "string" and sectionId ~= "" then selectedMap[sectionId] = true end
		end
	end
	for _, option in ipairs(sectionOptions or EMPTY) do
		if selectedMap == nil or selectedMap[option.value] then resolved[#resolved + 1] = option.value end
	end
	return resolved
end

function GF._preserveGroupCopyAnchorState(cfg)
	if type(cfg) ~= "table" then return nil end
	return {
		point = cfg.point,
		relativePoint = cfg.relativePoint,
		relativeTo = cfg.relativeTo,
		x = cfg.x,
		y = cfg.y,
	}
end

function GF._restoreGroupCopyAnchorState(cfg, anchorState)
	if type(cfg) ~= "table" or type(anchorState) ~= "table" then return end
	cfg.point = anchorState.point
	cfg.relativePoint = anchorState.relativePoint
	cfg.relativeTo = anchorState.relativeTo
	cfg.x = anchorState.x
	cfg.y = anchorState.y
end

function GF._copyGroupSourceToGroup(sourceKind, targetKind, sectionIds, keepAnchor, keepEnabled)
	local src = getCfg(sourceKind)
	local dest = getCfg(targetKind)
	if not (type(src) == "table" and type(dest) == "table") then return false end
	local anchorState = keepAnchor and GF._preserveGroupCopyAnchorState(dest) or nil
	local enabledValue = keepEnabled and dest.enabled
	local copied = false

	if type(sectionIds) == "table" and #sectionIds > 0 then
		for _, sectionId in ipairs(sectionIds) do
			local rules = GF._groupCopySectionRules[sectionId]
			if type(rules) == "table" then
				for _, path in ipairs(rules) do
					GF._groupCopyPathWithDefaults(sourceKind, src, dest, path)
				end
				copied = true
			end
		end
	else
		GF._groupCopyWipeTarget(dest)
		for key, value in pairs(src) do
			dest[key] = GF._groupCopyCloneValue(value)
		end
		copied = true
	end

	if not copied then return false end
	if keepAnchor then GF._restoreGroupCopyAnchorState(dest, anchorState) end
	if keepEnabled then dest.enabled = enabledValue end
	sanitizeHealthColorMode(dest)
	if GFH and GFH.SyncAurasEnabled then GFH.SyncAurasEnabled(dest) end
	return true
end

function GF._getUnitCopySourceConfig(unitKey)
	if type(unitKey) ~= "string" or unitKey == "" then return nil end
	if UF and UF.GetConfig then
		local cfg = UF.GetConfig(unitKey)
		if type(cfg) == "table" then return cfg end
		if unitKey == "boss" then
			cfg = UF.GetConfig("boss1")
			if type(cfg) == "table" then return cfg end
		end
	end
	local db = addon and addon.db and addon.db.ufFrames
	if type(db) ~= "table" then return nil end
	if unitKey == "boss" then return db.boss or db.boss1 end
	return db[unitKey]
end

function GF._copyUnitAuraIconsToGroup(sectionId, srcAuras, dest)
	if type(srcAuras) ~= "table" or type(dest) ~= "table" then return false end
	local ac = ensureAuraConfig(dest)
	local copied = false
	local resolved = UF and UF.ResolveSingleAuraConfig and UF.ResolveSingleAuraConfig(srcAuras) or nil
	local srcBuff = resolved and resolved.buff or nil
	local srcDebuff = resolved and resolved.debuff or nil

	if sectionId == "buffs" then
		local buff = ac.buff or {}
		ac.buff = buff
		local source = srcBuff or {}
		local enabled = source.enabled
		if enabled ~= nil then
			buff.enabled = enabled and true or false
			copied = true
		end
		if type(source.filterSelection) == "table" then
			buff.filterSelection = GF._sharedEdit.csm(source.filterSelection)
			copied = true
		elseif source.helpfulFilterMode ~= nil then
			buff.helpfulFilterMode = GF.NormalizeBuffHelpfulFilterMode(source.helpfulFilterMode)
			buff.filterSelection = GF.ExportGroupBuffSelection(buff)
			copied = true
		end
		if source.size ~= nil then
			buff.size = source.size
			copied = true
		end
		local perRow = tonumber(source.perRow)
		if perRow and perRow > 0 then
			buff.perRow = perRow
			copied = true
		end
		local maxCount = GF.ClampAuraCount(source.max)
		if maxCount ~= nil then
			buff.max = maxCount
			copied = true
		end
		local spacing = source.spacing
		if spacing == nil then spacing = source.padding end
		if spacing ~= nil then
			buff.spacing = spacing
			copied = true
		end
		local anchor = source.anchor
		if anchor == nil then anchor = source.anchorPoint end
		if anchor ~= nil then
			buff.anchorPoint = anchor
			copied = true
		end
		local offset = source.offset
		if type(offset) == "table" then
			buff.x = offset.x or 0
			buff.y = offset.y or 0
			copied = true
		end
		if source.showTooltip ~= nil then
			buff.showTooltip = source.showTooltip == true
			copied = true
		end
		local showCooldown = source.showCooldown
		if showCooldown ~= nil then
			buff.showCooldown = showCooldown and true or false
			copied = true
		end
		if source.showCooldownText ~= nil then
			buff.showCooldownText = source.showCooldownText == true
			copied = true
		end
		if source.borderTexture ~= nil then
			buff.borderTexture = source.borderTexture
			copied = true
		end
		if source.borderSize ~= nil then
			buff.borderSize = source.borderSize
			copied = true
		end
		if source.borderOffset ~= nil then
			buff.borderOffset = source.borderOffset
			copied = true
		end
		if source.countAnchor ~= nil then
			buff.countAnchor = source.countAnchor
			copied = true
		end
		if type(source.countOffset) == "table" then
			buff.countOffset = GF._groupCopyCloneValue(source.countOffset)
			copied = true
		end
		local countSize = source.countFontSize
		if countSize ~= nil then
			buff.countFontSize = countSize
			copied = true
		end
		if source.countFontOutline ~= nil then
			buff.countFontOutline = source.countFontOutline
			copied = true
		end
		local cooldownSize = source.cooldownFontSize
		if cooldownSize ~= nil then
			buff.cooldownFontSize = cooldownSize
			copied = true
		end
	elseif sectionId == "debuffs" then
		local debuff = ac.debuff or {}
		ac.debuff = debuff
		local source = srcDebuff or {}
		local enabled = source.enabled
		if enabled ~= nil then
			debuff.enabled = enabled and true or false
			copied = true
		end
		if type(source.filterSelection) == "table" then
			debuff.filterSelection = GF._sharedEdit.csm(source.filterSelection)
			copied = true
		end
		local debuffSize = source.size
		if debuffSize ~= nil then
			debuff.size = debuffSize
			copied = true
		end
		local perRow = tonumber(source.perRow)
		if perRow and perRow > 0 then
			debuff.perRow = perRow
			copied = true
		end
		local maxCount = GF.ClampAuraCount(source.max)
		if maxCount ~= nil then
			debuff.max = maxCount
			copied = true
		end
		local spacing = source.spacing
		if spacing == nil then spacing = source.padding end
		if spacing ~= nil then
			debuff.spacing = spacing
			copied = true
		end
		local anchor = source.anchor
		if anchor == nil then anchor = source.anchorPoint end
		local offset = source.offset
		if anchor ~= nil then
			debuff.anchorPoint = anchor
			copied = true
		end
		if type(offset) == "table" then
			debuff.x = offset.x or 0
			debuff.y = offset.y or 0
			copied = true
		end
		if source.showTooltip ~= nil then
			debuff.showTooltip = source.showTooltip == true
			copied = true
		end
		local showCooldown = source.showCooldown
		if showCooldown ~= nil then
			debuff.showCooldown = showCooldown and true or false
			copied = true
		end
		if source.showCooldownText ~= nil then
			debuff.showCooldownText = source.showCooldownText == true
			copied = true
		end
		if source.borderTexture ~= nil then
			debuff.borderTexture = source.borderTexture
			copied = true
		end
		if source.borderSize ~= nil then
			debuff.borderSize = source.borderSize
			copied = true
		end
		if source.borderOffset ~= nil then
			debuff.borderOffset = source.borderOffset
			copied = true
		end
		if source.blizzardDispelBorder ~= nil then
			debuff.showDispelIcon = source.blizzardDispelBorder == true
			copied = true
		end
		if source.countAnchor ~= nil then
			debuff.countAnchor = source.countAnchor
			copied = true
		end
		if type(source.countOffset) == "table" then
			debuff.countOffset = GF._groupCopyCloneValue(source.countOffset)
			copied = true
		end
		local countSize = source.countFontSize
		if countSize ~= nil then
			debuff.countFontSize = countSize
			copied = true
		end
		if source.countFontOutline ~= nil then
			debuff.countFontOutline = source.countFontOutline
			copied = true
		end
		local cooldownSize = source.cooldownFontSize
		if cooldownSize ~= nil then
			debuff.cooldownFontSize = cooldownSize
			copied = true
		end
	end

	if copied and GFH and GFH.SyncAurasEnabled then GFH.SyncAurasEnabled(dest) end
	return copied
end

function GF._copyUnitSourceSectionToGroup(sectionId, src, dest)
	if type(src) ~= "table" or type(dest) ~= "table" then return false end
	if sectionId == "frame" then
		local copied = false
		if src.width ~= nil then
			dest.width = src.width
			copied = true
		end
		if src.height ~= nil then
			dest.height = src.height
			copied = true
		end
		if src.powerHeight ~= nil then
			dest.powerHeight = src.powerHeight
			copied = true
		end
		if src.hideInClientScene ~= nil then
			dest.hideInClientScene = src.hideInClientScene and true or false
			copied = true
		end
		if src.showTooltip ~= nil or src.tooltipUseEditMode ~= nil then
			dest.tooltip = dest.tooltip or {}
			if src.showTooltip ~= nil then dest.tooltip.mode = src.showTooltip and "ALWAYS" or "OFF" end
			if src.tooltipUseEditMode ~= nil then dest.tooltip.useEditMode = src.tooltipUseEditMode and true or false end
			copied = true
		end
		return copied
	elseif sectionId == "border" then
		if src.border == nil then return false end
		dest.border = GF._groupCopyCloneValue(src.border)
		return true
	elseif sectionId == "text" then
		local sc = src.status
		if type(sc) ~= "table" then return false end
		local copied = false
		dest.text = dest.text or {}
		dest.status = dest.status or {}
		if sc.nameColorMode ~= nil then
			dest.status.nameColorMode = sc.nameColorMode
			dest.text.useClassColor = tostring(sc.nameColorMode or ""):upper() == "CLASS"
			copied = true
		end
		if sc.nameColor ~= nil then
			dest.status.nameColor = GF._groupCopyCloneValue(sc.nameColor)
			copied = true
		end
		if sc.nameStrata ~= nil then
			dest.status.nameStrata = sc.nameStrata
			copied = true
		end
		if sc.nameFrameLevelOffset ~= nil then
			dest.status.nameFrameLevelOffset = sc.nameFrameLevelOffset
			copied = true
		end
		if sc.nameOffset ~= nil then
			dest.text.nameOffset = GF._groupCopyCloneValue(sc.nameOffset)
			copied = true
		end
		if sc.nameMaxChars ~= nil then
			dest.text.nameMaxChars = sc.nameMaxChars
			copied = true
		end
		if sc.fontSize ~= nil then
			dest.text.fontSize = sc.fontSize
			copied = true
		end
		if sc.font ~= nil then
			dest.text.font = sc.font
			copied = true
		end
		if sc.fontOutline ~= nil then
			dest.text.fontOutline = sc.fontOutline
			copied = true
		end
		return copied
	elseif sectionId == "health" then
		if type(src.health) ~= "table" then return false end
		dest.health = GF._groupCopyCloneValue(src.health)
		return true
	elseif sectionId == "incomingheal" then
		local hc = src.health
		if type(hc) ~= "table" then return false end
		dest.health = dest.health or {}
		local copied = false
		for _, field in ipairs({ "incomingHealEnabled", "showSampleIncomingHeal", "incomingHealTexture", "incomingHealColor" }) do
			if hc[field] ~= nil then
				dest.health[field] = GF._groupCopyCloneValue(hc[field])
				copied = true
			end
		end
		return copied
	elseif sectionId == "absorb" then
		local hc = src.health
		if type(hc) ~= "table" then return false end
		dest.health = dest.health or {}
		local copied = false
		for _, field in ipairs({
			"absorbEnabled",
			"showSampleAbsorb",
			"absorbTexture",
			"absorbReverseFill",
			"absorbDontOverflowHealthBar",
			"absorbOverlayHeight",
			"absorbOverlayAnchorTop",
			"absorbUseCustomColor",
			"absorbColor",
			"useAbsorbGlow",
		}) do
			if hc[field] ~= nil then
				dest.health[field] = GF._groupCopyCloneValue(hc[field])
				copied = true
			end
		end
		return copied
	elseif sectionId == "healabsorb" then
		local hc = src.health
		if type(hc) ~= "table" then return false end
		dest.health = dest.health or {}
		local copied = false
		for _, field in ipairs({
			"healAbsorbEnabled",
			"showSampleHealAbsorb",
			"healAbsorbTexture",
			"healAbsorbReverseFill",
			"healAbsorbDontOverflowHealthBar",
			"healAbsorbOverlayHeight",
			"healAbsorbOverlayAnchorTop",
			"healAbsorbUseCustomColor",
			"healAbsorbColor",
		}) do
			if hc[field] ~= nil then
				dest.health[field] = GF._groupCopyCloneValue(hc[field])
				copied = true
			end
		end
		return copied
	elseif sectionId == "level" then
		local sc = src.status
		if type(sc) ~= "table" then return false end
		dest.status = dest.status or {}
		local copied = false
		for _, field in ipairs({
			"levelEnabled",
			"hideLevelAtMax",
			"levelColorMode",
			"levelColor",
			"levelFontSize",
			"levelFont",
			"levelFontOutline",
			"levelAnchor",
			"levelStrata",
			"levelFrameLevelOffset",
		}) do
			if sc[field] ~= nil then
				dest.status[field] = GF._groupCopyCloneValue(sc[field])
				copied = true
			end
		end
		if sc.levelOffset ~= nil then
			dest.status.levelOffset = GF._groupCopyCloneValue(sc.levelOffset)
			copied = true
		end
		return copied
	elseif sectionId == "statustext" then
		local sc = src.status
		if type(sc) ~= "table" then return false end
		local copied = false
		dest.status = dest.status or {}
		if type(sc.unitStatus) == "table" then
			dest.status.unitStatus = GF._groupCopyCloneValue(sc.unitStatus)
			copied = true
			local usc = sc.unitStatus
			if usc.showGroup ~= nil or usc.groupFormat ~= nil or usc.groupFontSize ~= nil or usc.groupOffset ~= nil then
				dest.status.groupNumber = dest.status.groupNumber or {}
				if usc.showGroup ~= nil then dest.status.groupNumber.enabled = usc.showGroup and true or false end
				if usc.groupFormat ~= nil then dest.status.groupNumber.format = normalizeGroupNumberFormat(usc.groupFormat) or usc.groupFormat end
				if usc.groupFontSize ~= nil then dest.status.groupNumber.fontSize = usc.groupFontSize end
				if usc.groupOffset ~= nil then dest.status.groupNumber.offset = GF._groupCopyCloneValue(usc.groupOffset) end
			end
		end
		return copied
	elseif sectionId == "raidmarker" then
		local rc = src.raidIcon
		if type(rc) ~= "table" then return false end
		dest.status = dest.status or {}
		dest.status.raidIcon = dest.status.raidIcon or {}
		local dst = dest.status.raidIcon
		local copied = false
		if rc.enabled ~= nil then
			dst.enabled = rc.enabled and true or false
			copied = true
		end
		if rc.size ~= nil then
			dst.size = rc.size
			copied = true
		end
		if rc.point ~= nil then
			dst.point = rc.point
			dst.relativePoint = rc.point
			copied = true
		end
		if type(rc.offset) == "table" then
			dst.x = rc.offset.x or 0
			dst.y = rc.offset.y or 0
			copied = true
		end
		return copied
	elseif sectionId == "power" then
		local copied = false
		if type(src.power) == "table" then
			dest.power = GF._groupCopyCloneValue(src.power)
			copied = true
		end
		if src.powerHeight ~= nil then
			dest.powerHeight = src.powerHeight
			copied = true
		end
		return copied
	elseif sectionId == "buffs" or sectionId == "debuffs" then
		return GF._copyUnitAuraIconsToGroup(sectionId, src.auraIcons, dest)
	elseif sectionId == "privateAuras" then
		if type(src.privateAuras) ~= "table" then return false end
		dest.privateAuras = GF._groupCopyCloneValue(src.privateAuras)
		return true
	end
	return false
end

function GF._copyUnitSourceToGroup(sourceUnit, targetKind, sectionIds, keepAnchor, keepEnabled)
	local src = GF._getUnitCopySourceConfig(sourceUnit)
	local dest = getCfg(targetKind)
	if not (type(src) == "table" and type(dest) == "table") then return false end
	local anchorState = keepAnchor and GF._preserveGroupCopyAnchorState(dest) or nil
	local enabledValue = keepEnabled and dest.enabled
	local copied = false
	for _, sectionId in ipairs(sectionIds or EMPTY) do
		if GF._groupCopyUnitSections[sectionId] and GF._copyUnitSourceSectionToGroup(sectionId, src, dest) then copied = true end
	end
	if not copied then return false end
	if keepAnchor then GF._restoreGroupCopyAnchorState(dest, anchorState) end
	if keepEnabled then dest.enabled = enabledValue end
	sanitizeHealthColorMode(dest)
	if GFH and GFH.SyncAurasEnabled then GFH.SyncAurasEnabled(dest) end
	return true
end

function GF._applyGroupCopyRefresh(targetKind, editModeId)
	GF:ApplyHeaderAttributes(targetKind)
	GF:RefreshNames()
	GF:RefreshPowerVisibility()
	GF:RefreshRoleIcons()
	GF:RefreshGroupIcons()
	GF:RefreshStatusText()
	GF:RefreshRaidIcons()
	GF:RefreshStatusIcons()
	GF:RefreshDispelTint()
	GF:RefreshTargetHighlights()
	GF:RefreshTextStyles()
	GF:RefreshClientSceneVisibility()
	GF:RefreshHealerBuffPlacement(targetKind)
	if targetKind == "raid" then GF:RefreshGroupIndicators() end
	refreshAllAuras()
	if GF._previewActive and GF._previewActive[targetKind] then GF:UpdatePreviewLayout(targetKind) end
	-- Do not call EditMode:RefreshFrame here: it reapplies layout data via onApply.
	-- Instead, sync the runtime layout values so future applies (e.g. dragging the frame)
	-- do not overwrite freshly copied cfg values with stale ones.
	if editModeId and GF._syncGroupEditModeLayoutData then GF._syncGroupEditModeLayoutData(targetKind, editModeId) end
	if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
end

function GF._copyGroupSettingsFromSource(source, targetKind, opts)
	if type(source) ~= "table" or not GF._isGroupCopyKind(targetKind) then return false end
	opts = opts or {}
	local sectionOptions = GF._buildGroupCopySectionOptions(source, targetKind)
	if #sectionOptions == 0 then return false end
	local keepAnchor = opts.keepAnchor ~= false
	local keepEnabled = opts.keepEnabled ~= false
	if source.sourceType == GF._groupCopySourceGroup then
		if not GF._isGroupCopyKind(source.sourceKey) then return false end
		local sectionIds = GF._resolveGroupCopySectionSelection(sectionOptions, opts.sections)
		if #sectionIds == 0 then return false end
		return GF._copyGroupSourceToGroup(source.sourceKey, targetKind, sectionIds, keepAnchor, keepEnabled)
	elseif source.sourceType == GF._groupCopySourceUnit then
		local sectionIds = GF._resolveGroupCopySectionSelection(sectionOptions, opts.sections)
		if #sectionIds == 0 then return false end
		return GF._copyUnitSourceToGroup(source.sourceKey, targetKind, sectionIds, keepAnchor, keepEnabled)
	end
	return false
end

function GF._ensureGroupCopySectionCheckbox(dialog, index)
	dialog.eqolGroupCopySectionRows = dialog.eqolGroupCopySectionRows or {}
	local row = dialog.eqolGroupCopySectionRows[index]
	if row then return row end
	row = CreateFrame("CheckButton", nil, dialog, "UICheckButtonTemplate")
	row.Label = row:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
	row.Label:SetPoint("LEFT", row, "RIGHT", 1, 1)
	row.Label:SetJustifyH("LEFT")
	row.Label:SetWidth(255)
	row:SetHitRectInsets(0, -255, 0, 0)
	dialog.eqolGroupCopySectionRows[index] = row
	return row
end

function GF._ensureGroupCopyAllCheckbox(dialog)
	if dialog.eqolGroupCopyAllRow then return dialog.eqolGroupCopyAllRow end
	local row = CreateFrame("CheckButton", nil, dialog, "UICheckButtonTemplate")
	row.Label = row:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
	row.Label:SetPoint("LEFT", row, "RIGHT", 1, 1)
	row.Label:SetJustifyH("LEFT")
	row.Label:SetWidth(255)
	row:SetHitRectInsets(0, -255, 0, 0)
	row.Label:SetText(L["UFAllSettings"] or "All settings")
	row:SetScript("OnClick", function(self)
		local popup = self:GetParent()
		local payload = popup and popup.data
		if not payload then return end
		GF._ensureGroupCopySectionSelection(payload)
		local checked = self:GetChecked() == true
		for _, option in ipairs(payload.sectionOptions) do
			payload.sectionSelection[option.value] = checked
		end
		if popup.eqolRefreshGroupCopySelection then popup:eqolRefreshGroupCopySelection() end
	end)
	dialog.eqolGroupCopyAllRow = row
	return row
end

function GF._refreshGroupCopySelectionDialog(dialog)
	local payload = dialog and dialog.data
	if not payload then return end
	GF._ensureGroupCopySectionSelection(payload)
	local options = payload.sectionOptions or {}
	local popupHeight = 150 + ((#options + 1) * 22)
	if popupHeight < 190 then popupHeight = 190 end
	local maxPopupHeight = 540
	if UIParent and UIParent.GetHeight then
		local uiHeight = tonumber(UIParent:GetHeight())
		if uiHeight and uiHeight > 0 then maxPopupHeight = math.max(320, math.floor(uiHeight - 120)) end
	end
	if popupHeight > maxPopupHeight then popupHeight = maxPopupHeight end
	if StaticPopup_Resize then StaticPopup_Resize(dialog, 380, popupHeight) end

	local allRow = GF._ensureGroupCopyAllCheckbox(dialog)
	allRow:ClearAllPoints()
	if dialog.button1 then
		allRow:SetPoint("TOPLEFT", dialog.button1, "BOTTOMLEFT", -40, -16)
	else
		allRow:SetPoint("TOPLEFT", dialog, "TOPLEFT", 24, -72)
	end
	allRow:Show()

	local selectedCount = 0
	local lastAnchor = allRow
	for index, option in ipairs(options) do
		local row = GF._ensureGroupCopySectionCheckbox(dialog, index)
		row.sectionId = option.value
		row:ClearAllPoints()
		row:SetPoint("TOPLEFT", lastAnchor, "BOTTOMLEFT", 0, -2)
		row.Label:SetText(option.label or option.value)
		local checked = payload.sectionSelection[option.value] == true
		row:SetChecked(checked)
		if checked then selectedCount = selectedCount + 1 end
		row:SetScript("OnClick", function(self)
			local popup = self:GetParent()
			local popupPayload = popup and popup.data
			if not popupPayload then return end
			GF._ensureGroupCopySectionSelection(popupPayload)
			popupPayload.sectionSelection[self.sectionId] = self:GetChecked() == true
			if popup.eqolRefreshGroupCopySelection then popup:eqolRefreshGroupCopySelection() end
		end)
		row:Show()
		lastAnchor = row
	end

	local rows = dialog.eqolGroupCopySectionRows or {}
	for index = #options + 1, #rows do
		local row = rows[index]
		if row then row:Hide() end
	end
	local allSelected = #options > 0 and selectedCount == #options
	allRow:SetChecked(allSelected)
	if dialog.button1 and dialog.button1.SetEnabled then dialog.button1:SetEnabled(selectedCount > 0) end
end

function GF._showGroupCopySettingsPopup(source, targetKind, editModeId)
	if type(source) ~= "table" or not GF._isGroupCopyKind(targetKind) then return end
	local sectionOptions = GF._buildGroupCopySectionOptions(source, targetKind)
	if #sectionOptions == 0 then
		if GF._copyGroupSettingsFromSource(source, targetKind, { keepAnchor = true, keepEnabled = true }) then GF._applyGroupCopyRefresh(targetKind, editModeId) end
		return
	end

	StaticPopupDialogs[GF._groupCopyDialogKey] = StaticPopupDialogs[GF._groupCopyDialogKey]
		or {
			text = "%s",
			button1 = L["Copy"] or ACCEPT,
			button2 = CANCEL,
			hideOnEscape = true,
			timeout = 0,
			whileDead = 1,
			preferredIndex = 3,
			OnAccept = function(self, data)
				local payload = data or self.data
				if payload and payload.source and payload.targetKind then
					local selected = GF._getSelectedGroupCopySections(payload)
					if #selected == 0 then return end
					local copyOptions = { keepAnchor = true, keepEnabled = true, sections = selected }
					if GF._copyGroupSettingsFromSource(payload.source, payload.targetKind, copyOptions) then GF._applyGroupCopyRefresh(payload.targetKind, payload.editModeId) end
				end
			end,
			OnShow = function(self, data)
				self.data = data or self.data
				GF._ensureGroupCopySectionSelection(self.data or {})
				if not self.eqolRefreshGroupCopySelection then self.eqolRefreshGroupCopySelection = GF._refreshGroupCopySelectionDialog end
				self:eqolRefreshGroupCopySelection()
			end,
			OnHide = function(self)
				if self.eqolGroupCopyAllRow then self.eqolGroupCopyAllRow:Hide() end
				if self.eqolGroupCopySectionRows then
					for _, row in ipairs(self.eqolGroupCopySectionRows) do
						if row then row:Hide() end
					end
				end
			end,
		}

	local sourceLabel = GF._getGroupCopySourceLabel(source)
	local targetLabel = GF._getGroupCopyTargetLabel(targetKind)
	local dialog = StaticPopupDialogs[GF._groupCopyDialogKey]
	if not dialog then return end
	dialog.text = string.format("%s\n%s -> %s", L["Copy settings"] or "Copy settings", sourceLabel, targetLabel)
	StaticPopup_Show(GF._groupCopyDialogKey, nil, nil, {
		source = source,
		targetKind = targetKind,
		editModeId = editModeId,
		sectionOptions = sectionOptions,
		sectionSelection = {},
	})
end

GF._GROUP_ANCHOR_TARGET_ORDER = GF._GROUP_ANCHOR_TARGET_ORDER or { "party", "raid", "mt", "ma" }
GF._GROUP_ANCHOR_TARGET_FRAMES = GF._GROUP_ANCHOR_TARGET_FRAMES or {
	party = "EQOLUFPartyAnchor",
	raid = "EQOLUFRaidAnchor",
	mt = "EQOLUFMTAnchor",
	ma = "EQOLUFMAAnchor",
}

function GF.GetGroupAnchorFrameName(kind) return GF._GROUP_ANCHOR_TARGET_FRAMES[kind] end

function GF.GetGroupAnchorTargetKind(value)
	for kind, frameName in pairs(GF._GROUP_ANCHOR_TARGET_FRAMES) do
		if value == frameName then return kind end
	end
	return nil
end

function GF.GetGroupAnchorTargetLabel(value)
	if value == nil or value == "" or value == "UIParent" then return L["Screen (UIParent)"] or "Screen (UIParent)" end

	local kind = GF.GetGroupAnchorTargetKind(value)
	if kind == "party" then return PARTY or "Party" end
	if kind == "raid" then return RAID or "Raid" end
	if kind == "mt" then return COMPACT_UNIT_FRAME_PROFILE_DISPLAYMAINTANK or "Main Tank" end
	if kind == "ma" then return COMPACT_UNIT_FRAME_PROFILE_DISPLAYMAINASSIST or "Main Assist" end

	local sharedAnchors = addon.SharedAnchors
	if sharedAnchors and sharedAnchors.GetTargetLabel then return sharedAnchors:GetTargetLabel(value) end
	return value
end

function GF.BuildGroupAnchorExtraEntries(ownerKind)
	local entries = {}
	for _, kind in ipairs(GF._GROUP_ANCHOR_TARGET_ORDER) do
		if kind ~= ownerKind then entries[#entries + 1] = {
			key = GF.GetGroupAnchorFrameName(kind),
			label = GF.GetGroupAnchorTargetLabel(GF.GetGroupAnchorFrameName(kind)),
		} end
	end
	return entries
end

function GF.GetGroupAnchorTargetEntries(kind)
	local cfg = getCfg(kind)
	local current = (cfg and cfg.relativeTo) or "UIParent"
	local entries = {}
	local sharedAnchors = addon.SharedAnchors

	if sharedAnchors and sharedAnchors.GetEntries then
		local sharedEntries = sharedAnchors:GetEntries(current, {
			includeCursor = false,
			extraEntries = GF.BuildGroupAnchorExtraEntries(kind),
		})
		for i = 1, #sharedEntries do
			local entry = sharedEntries[i]
			local value = entry and entry.key
			if value then entries[#entries + 1] = {
				value = value,
				label = GF.GetGroupAnchorTargetLabel(value),
			} end
		end
		return entries
	end

	entries[#entries + 1] = { value = "UIParent", label = GF.GetGroupAnchorTargetLabel("UIParent") }
	for _, entry in ipairs(GF.BuildGroupAnchorExtraEntries(kind)) do
		entries[#entries + 1] = {
			value = entry.key,
			label = entry.label,
		}
	end
	return entries
end

function GF.WouldGroupAnchorLoop(kind, target)
	local ownerFrameName = GF.GetGroupAnchorFrameName(kind)
	if not ownerFrameName or type(target) ~= "string" or target == "" or target == "UIParent" then return false end

	local seen = {
		[ownerFrameName] = true,
	}
	local current = target

	while type(current) == "string" and current ~= "" and current ~= "UIParent" do
		if seen[current] then return true end
		seen[current] = true

		local currentKind = GF.GetGroupAnchorTargetKind(current)
		if not currentKind then return false end

		local cfg = getCfg(currentKind)
		current = cfg and cfg.relativeTo or "UIParent"
	end

	return false
end

function GF.WouldGroupAnchorLiveLoop(kind, target)
	local ownerFrameName = GF.GetGroupAnchorFrameName(kind)
	if not ownerFrameName or type(target) ~= "string" or target == "" or target == "UIParent" then return false end

	local ownerFrame = (GF.anchors and GF.anchors[kind]) or _G[ownerFrameName]
	if not ownerFrame then return false end

	local current
	local currentKind = GF.GetGroupAnchorTargetKind(target)
	local sharedAnchors = addon.SharedAnchors
	if currentKind then
		current = (GF.anchors and GF.anchors[currentKind]) or _G[target]
	elseif sharedAnchors and sharedAnchors.ResolveFrame then
		current = sharedAnchors:ResolveFrame(target)
	else
		current = _G[target]
	end

	if not current or current == UIParent then return false end
	if current == ownerFrame then return true end

	local visited = {}
	local limit = 16
	while current and current ~= UIParent and limit > 0 do
		if current == ownerFrame then return true end
		if visited[current] then break end
		visited[current] = true
		if not current.GetPoint then break end
		local _, relativeTo = current:GetPoint(1)
		if relativeTo == ownerFrame then return true end
		current = type(relativeTo) == "string" and _G[relativeTo] or relativeTo
		limit = limit - 1
	end

	if limit <= 0 then return true end
	return false
end

function GF.ValidateGroupAnchorTarget(kind, value, currentTarget, suppressLoopMessage)
	local current = type(currentTarget) == "string" and currentTarget or ((getCfg(kind) and getCfg(kind).relativeTo) or "UIParent")
	local target
	local sharedAnchors = addon.SharedAnchors

	if sharedAnchors and sharedAnchors.ValidateTarget then
		target = sharedAnchors:ValidateTarget(value, current, {
			includeCursor = false,
			extraEntries = GF.BuildGroupAnchorExtraEntries(kind),
		})
	else
		target = type(value) == "string" and value ~= "" and value or "UIParent"
		local known = false
		for _, entry in ipairs(GF.GetGroupAnchorTargetEntries(kind)) do
			if entry.value == target then
				known = true
				break
			end
		end
		if not known then target = "UIParent" end
	end

	if target == GF.GetGroupAnchorFrameName(kind) then target = "UIParent" end

	if GF.WouldGroupAnchorLoop(kind, target) or GF.WouldGroupAnchorLiveLoop(kind, target) then
		if not suppressLoopMessage then
			print("|cff00ff98Enhance QoL|r: " .. (L["AnchorLoop"] or 'Anchor loop detected for "%s". Resetting to UIParent.'):format(GF.GetGroupAnchorTargetLabel(target)))
		end
		return "UIParent"
	end

	return target
end

function GF.GetGroupAnchorTargetValue(kind)
	local cfg = getCfg(kind)
	local current = (cfg and cfg.relativeTo) or "UIParent"
	return GF.ValidateGroupAnchorTarget(kind, current, current, true)
end

function GF.ResolveGroupAnchorTargetFrame(kind, value)
	local target = GF.ValidateGroupAnchorTarget(kind, value, value, true)
	local targetKind = GF.GetGroupAnchorTargetKind(target)
	local sharedAnchors = addon.SharedAnchors
	if targetKind then return (GF.anchors and GF.anchors[targetKind]) or _G[target] or UIParent end
	if sharedAnchors and sharedAnchors.ResolveFrame then return sharedAnchors:ResolveFrame(target) end
	if target == "UIParent" then return UIParent end
	return _G[target] or UIParent
end

function GF.SetGroupAnchorPointFromCfg(frame, kind, cfg)
	if not (frame and cfg) then return end

	local rawTarget = cfg.relativeTo
	local target
	if frame._eqolAnchorRawTarget == rawTarget and frame._eqolAnchorValidatedTarget then
		target = frame._eqolAnchorValidatedTarget
	else
		target = GF.ValidateGroupAnchorTarget(kind, rawTarget, rawTarget, true)
		if cfg.relativeTo ~= target then
			cfg.relativeTo = target
			rawTarget = target
		end
		frame._eqolAnchorRawTarget = rawTarget
		frame._eqolAnchorValidatedTarget = target
	end

	local point = cfg.point or "CENTER"
	local relativePoint = cfg.relativePoint or point
	local x = tonumber(cfg.x) or 0
	local y = tonumber(cfg.y) or 0
	local relativeFrame = GF.ResolveGroupAnchorTargetFrame(kind, target)
	local anchorKey = tostring(target or "") .. "\031" .. tostring(relativeFrame) .. "\031" .. tostring(point or "") .. "\031" .. tostring(relativePoint or "") .. "\031" .. tostring(x) .. "\031" .. tostring(y)
	if frame._eqolAnchorPointKey == anchorKey then return end
	frame._eqolAnchorPointKey = anchorKey

	frame:ClearAllPoints()
	if Pixel and Pixel.SetPoint then
		Pixel.SetPoint(frame, point, relativeFrame, relativePoint, x, y)
	else
		frame:SetPoint(point, relativeFrame, relativePoint, x, y)
	end
	if addon.MythicPlus and addon.MythicPlus.functions and addon.MythicPlus.functions.ReapplyTrackerAnchorsForTarget and frame.GetName then
		addon.MythicPlus.functions.ReapplyTrackerAnchorsForTarget(frame:GetName())
	end
end

local function anchorUsesUIParent(kind) return GF.GetGroupAnchorTargetValue(kind) == "UIParent" end

function GF.ReadStatusIconField(kind, key, field, fallback)
	local cfg = getCfg(kind)
	local iconCfg = cfg and cfg.status and cfg.status[key]
	if iconCfg and iconCfg[field] ~= nil then return iconCfg[field] end
	local defCfg = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status[key]
	if defCfg and defCfg[field] ~= nil then return defCfg[field] end
	return fallback
end

function GF.SetStatusIconField(kind, key, field, value)
	local cfg = getCfg(kind)
	if not cfg then return nil end
	cfg.status = cfg.status or {}
	cfg.status[key] = cfg.status[key] or {}
	cfg.status[key][field] = value
	return cfg.status[key][field]
end

function GF:AppendStatusIconSettings(settings, kind, editModeId, insertIndex)
	local iconMeta = GFH.STATUS_ICON_EDITMODE_META
	if not (settings and SettingType and iconMeta and iconMeta[1]) then return end
	local iconLabelByKey = {
		readyCheckIcon = L["UFGroupStatusIconReadyCheck"] or "Ready check icon",
		summonIcon = L["UFGroupStatusIconSummon"] or "Summon icon",
		resurrectIcon = L["UFGroupStatusIconResurrect"] or "Resurrect icon",
		phaseIcon = L["UFGroupStatusIconPhase"] or "Phasing icon",
	}
	local showFormat = L["UFGroupStatusIconsShowFormat"] or "Show %s"
	local sampleFormat = L["UFGroupStatusIconsSampleFormat"] or "%s sample"
	local sizeFormat = L["UFGroupStatusIconsSizeFormat"] or "%s size"
	local anchorFormat = L["UFGroupStatusIconsAnchorFormat"] or "%s anchor"
	local offsetXFormat = L["UFGroupStatusIconsOffsetXFormat"] or "%s offset X"
	local offsetYFormat = L["UFGroupStatusIconsOffsetYFormat"] or "%s offset Y"
	local function push(entry)
		if insertIndex and insertIndex > 0 then
			table.insert(settings, insertIndex, entry)
			insertIndex = insertIndex + 1
		else
			settings[#settings + 1] = entry
		end
	end

	push({
		name = L["UFGroupStatusIcons"] or "Status icons",
		kind = SettingType.Collapsible,
		id = "statusicons",
		defaultCollapsed = true,
	})

	for index, meta in ipairs(iconMeta) do
		if index > 1 then push({ name = "", kind = SettingType.Divider, parentId = "statusicons" }) end
		local key = meta.key
		local label = iconLabelByKey[key] or meta.name or key
		local defaultPoint = meta.defaultPoint or "CENTER"
		local defaultSize = tonumber(meta.defaultSize) or 16
		local enabledField = key .. "Enabled"
		local sampleField = key .. "Sample"
		local sizeField = key .. "Size"
		local pointField = key .. "Point"
		local offsetXField = key .. "OffsetX"
		local offsetYField = key .. "OffsetY"

		push({
			name = showFormat:format(label),
			kind = SettingType.Checkbox,
			field = enabledField,
			parentId = "statusicons",
			get = function() return GF.ReadStatusIconField(kind, key, "enabled", true) ~= false end,
			set = function(_, value)
				local stored = GF.SetStatusIconField(kind, key, "enabled", value and true or false)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, enabledField, stored, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				GF:RefreshStatusIcons()
			end,
		})
		push({
			name = sampleFormat:format(label),
			kind = SettingType.Checkbox,
			field = sampleField,
			parentId = "statusicons",
			get = function() return GF.ReadStatusIconField(kind, key, "sample", false) == true end,
			set = function(_, value)
				local stored = GF.SetStatusIconField(kind, key, "sample", value and true or false)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, sampleField, stored, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				GF:RefreshStatusIcons()
			end,
			isEnabled = function() return GF.ReadStatusIconField(kind, key, "enabled", true) ~= false end,
		})
		push({
			name = sizeFormat:format(label),
			kind = SettingType.Slider,
			allowInput = true,
			field = sizeField,
			parentId = "statusicons",
			minValue = 8,
			maxValue = 40,
			valueStep = 1,
			get = function() return GF.ReadStatusIconField(kind, key, "size", defaultSize) end,
			set = function(_, value)
				local current = GF.ReadStatusIconField(kind, key, "size", defaultSize)
				local stored = GF.SetStatusIconField(kind, key, "size", clampNumber(value, 8, 40, current))
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, sizeField, stored, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				GF:RefreshStatusIcons()
			end,
			isEnabled = function() return GF.ReadStatusIconField(kind, key, "enabled", true) ~= false end,
		})
		push({
			name = anchorFormat:format(label),
			kind = SettingType.Dropdown,
			field = pointField,
			parentId = "statusicons",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function() return GF.ReadStatusIconField(kind, key, "point", defaultPoint) end,
			set = function(_, value)
				local stored = GF.SetStatusIconField(kind, key, "point", value)
				GF.SetStatusIconField(kind, key, "relativePoint", value)
				local storedX = GF.SetStatusIconField(kind, key, "x", 0)
				local storedY = GF.SetStatusIconField(kind, key, "y", 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, pointField, stored, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, offsetXField, storedX, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, offsetYField, storedY, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				GF:RefreshStatusIcons()
			end,
			isEnabled = function() return GF.ReadStatusIconField(kind, key, "enabled", true) ~= false end,
		})
		push({
			name = offsetXFormat:format(label),
			kind = SettingType.Slider,
			allowInput = true,
			field = offsetXField,
			parentId = "statusicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function() return GF.ReadStatusIconField(kind, key, "x", 0) end,
			set = function(_, value)
				local current = GF.ReadStatusIconField(kind, key, "x", 0)
				local stored = GF.SetStatusIconField(kind, key, "x", clampNumber(value, -200, 200, current))
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, offsetXField, stored, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				GF:RefreshStatusIcons()
			end,
			isEnabled = function() return GF.ReadStatusIconField(kind, key, "enabled", true) ~= false end,
		})
		push({
			name = offsetYFormat:format(label),
			kind = SettingType.Slider,
			allowInput = true,
			field = offsetYField,
			parentId = "statusicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function() return GF.ReadStatusIconField(kind, key, "y", 0) end,
			set = function(_, value)
				local current = GF.ReadStatusIconField(kind, key, "y", 0)
				local stored = GF.SetStatusIconField(kind, key, "y", clampNumber(value, -200, 200, current))
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, offsetYField, stored, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				GF:RefreshStatusIcons()
			end,
			isEnabled = function() return GF.ReadStatusIconField(kind, key, "enabled", true) ~= false end,
		})
	end
end

function GF.ReorderGroupEditModeSettings(settings)
	if not (settings and SettingType) then return settings end

	local commonSectionOrder = {
		"utility",
		"frame",
		"layout",
		"border",
		"hoverHighlight",
		"aggroHighlight",
		"targetHighlight",
		"portrait",
		"text",
		"dataBar",
		"health",
		"incomingheal",
		"absorb",
		"healabsorb",
		"power",
		"level",
		"statustext",
		"rangeFade",
		"dispeltint",
		"raidmarker",
		"buffs",
		"debuffs",
		"privateAuras",
	}
	local specificSectionOrder = {
		"party",
		"raid",
		"partyGroupBorder",
		"groupicons",
		"statusicons",
		"roleicons",
		"externals",
	}

	local sectionHeaderIndexById = {}
	local encounteredSectionIds = {}
	for i = 1, #settings do
		local entry = settings[i]
		if type(entry) == "table" and entry.kind == SettingType.Collapsible and type(entry.id) == "string" and sectionHeaderIndexById[entry.id] == nil then
			sectionHeaderIndexById[entry.id] = i
			encounteredSectionIds[#encounteredSectionIds + 1] = entry.id
		end
	end

	local orderedSectionIds = {}
	local seenSectionIds = {}
	for i = 1, #commonSectionOrder do
		local id = commonSectionOrder[i]
		if sectionHeaderIndexById[id] then
			orderedSectionIds[#orderedSectionIds + 1] = id
			seenSectionIds[id] = true
		end
	end
	local firstSpecificSectionIndex
	for i = 1, #specificSectionOrder do
		local id = specificSectionOrder[i]
		if sectionHeaderIndexById[id] then
			if not firstSpecificSectionIndex then firstSpecificSectionIndex = #orderedSectionIds + 1 end
			orderedSectionIds[#orderedSectionIds + 1] = id
			seenSectionIds[id] = true
		end
	end
	for i = 1, #encounteredSectionIds do
		local id = encounteredSectionIds[i]
		if not seenSectionIds[id] then
			orderedSectionIds[#orderedSectionIds + 1] = id
			seenSectionIds[id] = true
		end
	end

	local orderedSettings = {}
	local appended = {}
	for i = 1, #orderedSectionIds do
		if firstSpecificSectionIndex and i == firstSpecificSectionIndex and #orderedSettings > 0 then orderedSettings[#orderedSettings + 1] = { name = "", kind = SettingType.Divider } end
		local id = orderedSectionIds[i]
		local headerIndex = sectionHeaderIndexById[id]
		if headerIndex and not appended[headerIndex] then
			orderedSettings[#orderedSettings + 1] = settings[headerIndex]
			appended[headerIndex] = true
		end
		for j = 1, #settings do
			if not appended[j] then
				local entry = settings[j]
				if type(entry) == "table" and entry.parentId == id then
					orderedSettings[#orderedSettings + 1] = entry
					appended[j] = true
				end
			end
		end
	end

	for i = 1, #settings do
		if not appended[i] then orderedSettings[#orderedSettings + 1] = settings[i] end
	end

	return orderedSettings
end

local function buildEditModeSettings(kind, editModeId)
	if not SettingType then return nil end

	local widthLabel = HUD_EDIT_MODE_SETTING_CHAT_FRAME_WIDTH or "Width"
	local heightLabel = HUD_EDIT_MODE_SETTING_CHAT_FRAME_HEIGHT or "Height"
	local raidLikeKind = isRaidLikeKind(kind)
	local raidKind = kind == "raid"
	local splitRoleKind = kind == "mt" or kind == "ma"
	local specOptions = GFH.BuildSpecOptions()
	local tooltipModeOptions = {
		{ value = "OFF", label = L["Off"] or "Off" },
		{ value = "ALWAYS", label = L["Always"] or "Always" },
		{ value = "OUT_OF_COMBAT", label = L["Out of combat"] or "Out of combat" },
		{ value = "MODIFIER", label = L["Only with modifier"] or "Only with modifier" },
	}
	local tooltipModifierOptions = {
		{ value = "ALT", label = ALT_KEY_TEXT },
		{ value = "SHIFT", label = SHIFT_KEY_TEXT },
		{ value = "CTRL", label = CTRL_KEY_TEXT },
	}
	local targetHighlightLayerOptions = {
		{ value = "ABOVE_BORDER", label = L["UFTargetHighlightLayerAboveBorder"] or "Above border" },
		{ value = "BEHIND_BORDER", label = L["UFTargetHighlightLayerBehindBorder"] or "Behind border" },
	}
	local function getTooltipModeValue()
		local cfg = getCfg(kind)
		local tc = cfg and cfg.tooltip or {}
		return tc.mode or (DEFAULTS[kind] and DEFAULTS[kind].tooltip and DEFAULTS[kind].tooltip.mode) or "OFF"
	end
	local function getTooltipModeLabel()
		local mode = tostring(getTooltipModeValue() or "OFF"):upper()
		for _, option in ipairs(tooltipModeOptions) do
			if option.value == mode then return option.label end
		end
		return mode
	end
	local function getTooltipModifierValue()
		local cfg = getCfg(kind)
		local tc = cfg and cfg.tooltip or {}
		return tc.modifier or (DEFAULTS[kind] and DEFAULTS[kind].tooltip and DEFAULTS[kind].tooltip.modifier) or "ALT"
	end
	local function getTooltipModifierLabel()
		local modifier = tostring(getTooltipModifierValue() or "ALT"):upper()
		for _, option in ipairs(tooltipModifierOptions) do
			if option.value == modifier then return option.label end
		end
		return modifier
	end
	local function tooltipModeGenerator()
		return function(_, root, data)
			for _, option in ipairs(tooltipModeOptions) do
				root:CreateRadio(option.label, function() return data.get and data.get() == option.value end, function()
					if data.set then data.set(nil, option.value) end
					data.customDefaultText = option.label
					if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
				end)
			end
		end
	end
	local function tooltipModifierGenerator()
		return function(_, root, data)
			for _, option in ipairs(tooltipModifierOptions) do
				root:CreateRadio(option.label, function() return data.get and data.get() == option.value end, function()
					if data.set then data.set(nil, option.value) end
					data.customDefaultText = option.label
					if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
				end)
			end
		end
	end
	local function auraIconShapeSetting(typeKey, field, parentId)
		return {
			name = L["settingsIconShapeLabel"] or "Icon shape",
			kind = SettingType.Dropdown,
			field = field,
			parentId = parentId,
			height = 120,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local typeCfg = ac and ac[typeKey] or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras[typeKey]) or {}
				return GF.NormalizeAuraIconShape(typeCfg.iconShape, def.iconShape or "DEFAULT")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local typeCfg = ac[typeKey]
				local shape = GF.NormalizeAuraIconShape(value, "DEFAULT")
				typeCfg.iconShape = shape
				typeCfg.borderTexture = GF.NormalizeAuraBorderTexture(typeCfg.borderTexture, "DEFAULT", shape)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, field, shape, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			generator = function(_, root, data)
					local options = addon.IconShape and addon.IconShape.GetOptions and addon.IconShape.GetOptions(L) or {
						{ value = "DEFAULT", label = L["settingsIconShapeDefault"] or DEFAULT or "Default" },
						{ value = "SQUARE", label = L["settingsIconShapeSquare"] or "Square" },
						{ value = "ROUND", label = L["settingsIconShapeRound"] or "Round" },
						{ value = "ROUND_STAR", label = L["settingsIconShapeRoundStar"] or "Round star" },
						{ value = "HEXAGON", label = L["settingsIconShapeHexagon"] or "Hexagon" },
						{ value = "DIAMOND", label = L["settingsIconShapeDiamond"] or "Diamond" },
					}
				for _, option in ipairs(options) do
					root:CreateRadio(option.label, function() return data.get and data.get() == option.value end, function()
						if data.set then data.set(nil, option.value) end
						data.customDefaultText = option.label
					end)
				end
			end,
		}
	end
	local sortGroupOptions = {
		{ value = "GROUP", label = L["Group"] or "Group" },
		{ value = "ASSIGNEDROLE", label = L["Role"] or "Role" },
	}
	local sortMethodOptions = {
		{ value = "INDEX", label = L["Index"] or "Index" },
		{ value = "NAME", label = L["Name"] or "Name" },
		{ value = "CUSTOM", label = CUSTOM or "Custom" },
	}
	local sortDirOptions = {
		{ value = "ASC", label = L["Ascending"] or "Ascending" },
		{ value = "DESC", label = L["Descending"] or "Descending" },
	}
	local privateAuraPointOptions = {
		{ value = "LEFT", label = "Left", text = "Left" },
		{ value = "RIGHT", label = "Right", text = "Right" },
		{ value = "TOP", label = "Top", text = "Top" },
		{ value = "BOTTOM", label = "Bottom", text = "Bottom" },
	}
	local auraRendererOptions = {
		{ value = "CUSTOM", label = L["UFAuraRendererCustom"] or "EnhanceQoL", text = L["UFAuraRendererCustom"] or "EnhanceQoL" },
		{ value = "BLIZZARD", label = L["UFAuraRendererBlizzard"] or "Blizzard", text = L["UFAuraRendererBlizzard"] or "Blizzard" },
	}
	local blizzardAuraTypeOptions = {
		{ value = "buffs", label = L["Buffs"] or "Buffs", text = L["Buffs"] or "Buffs" },
		{ value = "debuffs", label = L["Debuffs"] or "Debuffs", text = L["Debuffs"] or "Debuffs" },
		{ value = "dispels", label = L["UFDispelIndicator"] or "Dispel indicator", text = L["UFDispelIndicator"] or "Dispel indicator" },
		{ value = "externals", label = L["Externals"] or "Externals", text = L["Externals"] or "Externals" },
		{ value = "privateAuras", label = L["Private Auras"] or "Private Auras", text = L["Private Auras"] or "Private Auras" },
	}
	local defPrivateAuras = (DEFAULTS[kind] and DEFAULTS[kind].privateAuras) or {}
	local function ensurePrivateAuraConfig(cfg)
		if not cfg then return nil end
		cfg.privateAuras = cfg.privateAuras or {}
		cfg.privateAuras.icon = cfg.privateAuras.icon or {}
		cfg.privateAuras.parent = cfg.privateAuras.parent or {}
		cfg.privateAuras.duration = cfg.privateAuras.duration or {}
		return cfg.privateAuras
	end
	local function isPrivateAurasEnabled()
		local cfg = getCfg(kind)
		local pcfg = cfg and cfg.privateAuras or {}
		if pcfg.enabled == nil then return defPrivateAuras.enabled == true end
		return pcfg.enabled == true
	end
	local function getAuraRendererValue()
		local cfg = getCfg(kind)
		return GF.GetGroupAuraRenderer(cfg, DEFAULTS[kind] or EMPTY)
	end
	local function isBlizzardRendererSelected() return getAuraRendererValue() == "BLIZZARD" end
	local function normalizeGroupBy(value)
		if value == nil then return nil end
		local v = tostring(value):upper()
		if v == "ROLE" then v = "ASSIGNEDROLE" end
		if v == "CLASS" then return "GROUP" end
		if v == "GROUP" or v == "ASSIGNEDROLE" then return v end
		return nil
	end
	local function getGroupByValue()
		local cfg = getCfg(kind)
		return normalizeGroupBy(cfg and cfg.groupBy) or (DEFAULTS[kind] and DEFAULTS[kind].groupBy) or "GROUP"
	end
	local function isCustomSortingEnabled()
		if kind ~= "raid" and kind ~= "party" then return false end
		local cfg = getCfg(kind)
		return resolveSortMethod(cfg) == "NAMELIST"
	end
	local function isCustomSortEditorOpen() return GF._customSortEditor and GF._customSortEditor.IsShown and GF._customSortEditor:IsShown() end
	local function getGroupByLabel()
		local mode = getGroupByValue()
		for _, option in ipairs(sortGroupOptions) do
			if option.value == mode then return option.label end
		end
		return mode
	end
	local function isRaidGroupFilterShown() return kind == "raid" and getGroupByValue() == "GROUP" end
	local function updateRaidGroupFilter(group, value)
		local cfg = getCfg(kind)
		if not cfg then return end
		local changed = GF.SetRaidGroupShown(cfg, group, value and true or false)
		if not changed then
			if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
			return
		end
		GF.SyncRaidGroupFilterEditModeValues(editModeId, cfg)
		GF:ApplyHeaderAttributes(kind)
		if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
		if kind == "raid" then GF:RefreshGroupIndicators() end
		if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
	end
	local function applyGroupByPreset(value)
		local cfg = getCfg(kind)
		if not cfg then return end
		local groupBy = normalizeGroupBy(value) or "GROUP"
		cfg.groupBy = groupBy
		if groupBy == "ASSIGNEDROLE" then
			cfg.groupingOrder = GFH.ROLE_ORDER
			cfg.groupFilter = nil
		else
			cfg.groupingOrder = GFH.GROUP_ORDER
			cfg.groupFilter = nil
		end
		if EditMode and EditMode.SetValue then
			EditMode:SetValue(editModeId, "groupBy", cfg.groupBy, nil, true)
			EditMode:SetValue(editModeId, "groupingOrder", cfg.groupingOrder, nil, true)
			GF.SyncRaidGroupFilterEditModeValues(editModeId, cfg)
			local sortMethodValue = resolveSortMethod(cfg)
			if sortMethodValue == "NAMELIST" then sortMethodValue = "CUSTOM" end
			EditMode:SetValue(editModeId, "sortMethod", sortMethodValue or "INDEX", nil, true)
		end
		GF:ApplyHeaderAttributes(kind)
		if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
		if kind == "raid" then GF:RefreshGroupIndicators() end
	end
	local function getSortMethodValue()
		local cfg = getCfg(kind)
		local v = resolveSortMethod(cfg)
		if v == "NAMELIST" then return "CUSTOM" end
		if v == "NAME" or v == "INDEX" then return v end
		return "INDEX"
	end
	local function getSortMethodLabel()
		local mode = getSortMethodValue()
		for _, option in ipairs(sortMethodOptions) do
			if option.value == mode then return option.label end
		end
		return mode
	end
	local function getSortDirValue()
		local cfg = getCfg(kind)
		return (cfg and cfg.sortDir) or (DEFAULTS[kind] and DEFAULTS[kind].sortDir) or "ASC"
	end
	local function getSortDirLabel()
		local mode = getSortDirValue()
		for _, option in ipairs(sortDirOptions) do
			if option.value == mode then return option.label end
		end
		return mode
	end
	local function getSplitRoleViewerRoleValue()
		local cfg = getCfg(kind)
		return GF.NormalizeViewerRoleFilter(cfg and cfg.viewerRoleFilter)
	end
	local function getSplitRoleViewerRoleLabel() return GF.DropdownOptionLabel(GF.splitRoleViewerRoleOptions, getSplitRoleViewerRoleValue(), L["All"] or "All") end
	local function getGroupNumberEnabledValue()
		local cfg = getCfg(kind)
		local def = DEFAULTS[kind] or {}
		return resolveGroupNumberSettingEnabled(cfg, def)
	end
	local function getGroupFormatValue()
		local cfg = getCfg(kind)
		local def = DEFAULTS[kind] or {}
		return resolveGroupNumberFormat(cfg, def)
	end
	local function getGroupFormatLabel()
		local fmt = getGroupFormatValue()
		return GF._groupNumberFormatCache.labelByValue[fmt] or tostring(fmt)
	end
	local function isStatusTextEnabled()
		local cfg = getCfg(kind)
		local sc = cfg and cfg.status or {}
		local us = sc.unitStatus or {}
		return us.enabled ~= false
	end
	local function isGroupNumberSettingsEnabled() return isStatusTextEnabled() and getGroupNumberEnabledValue() end
	local function isGroupIndicatorShown()
		if kind ~= "raid" then return false end
		return getGroupByValue() == "GROUP"
	end
	local function getGroupIndicatorEnabledValue()
		local cfg = getCfg(kind)
		local def = DEFAULTS[kind] or {}
		return resolveGroupIndicatorEnabled(cfg, def)
	end
	local function getGroupIndicatorFormatValue()
		local cfg = getCfg(kind)
		local def = DEFAULTS[kind] or {}
		return resolveGroupIndicatorFormat(cfg, def)
	end
	local function getGroupIndicatorFormatLabel()
		local fmt = getGroupIndicatorFormatValue()
		return GF._groupNumberFormatCache.labelByValue[fmt] or tostring(fmt)
	end
	local function isGroupIndicatorSettingsEnabled() return isGroupIndicatorShown() and getGroupIndicatorEnabledValue() end
	local function getRangeFadeConfig()
		local cfg = getCfg(kind)
		local sc = cfg and cfg.status or {}
		local rf = sc.rangeFade or {}
		local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.rangeFade) or {}
		return rf, def
	end
	local function isRangeFadeEnabled()
		local rf, def = getRangeFadeConfig()
		if rf.enabled == nil then return def.enabled ~= false end
		return rf.enabled == true
	end
	local rangeFadeOfflineOpacityLabel = (PLAYER_OFFLINE or "Offline") .. " " .. (OPACITY or "Opacity")
	local function getHealthTextMode(key, fallback)
		local cfg = getCfg(kind)
		local hc = cfg and cfg.health or {}
		local def = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
		return hc[key] or def[key] or fallback or "NONE"
	end
	local function isHealthTextEnabled(key, fallback) return getHealthTextMode(key, fallback) ~= "NONE" end
	local function getPowerTextMode(key, fallback)
		local cfg = getCfg(kind)
		local pcfg = cfg and cfg.power or {}
		local def = (DEFAULTS[kind] and DEFAULTS[kind].power) or {}
		return pcfg[key] or def[key] or fallback or "NONE"
	end
	local function isPowerDetached()
		local cfg = getCfg(kind)
		return GF.IsGroupPowerDetached(cfg, kind)
	end
	local function isDetachedPowerWidthUnlocked()
		if not isPowerDetached() then return false end
		local cfg = getCfg(kind)
		local pcfg = cfg and cfg.power or {}
		local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
		local value = pcfg.detachedMatchHealthWidth
		if value == nil then value = def.detachedMatchHealthWidth end
		if value == nil then value = true end
		return value ~= true
	end
	local function isDetachedPowerBorderEnabled()
		if not isPowerDetached() then return false end
		local cfg = getCfg(kind)
		local pcfg = cfg and cfg.power or {}
		return pcfg.detachedBorder == true
	end
	local function isDataBarEnabled()
		local cfg = getCfg(kind)
		local dbc = cfg and cfg.dataBar or {}
		return dbc.enabled == true
	end
	local function isDataBarDetached()
		if not isDataBarEnabled() then return false end
		local cfg = getCfg(kind)
		local dbc = cfg and cfg.dataBar or {}
		return dbc.detached == true
	end
	local function isDetachedDataBarWidthUnlocked()
		if not isDataBarDetached() then return false end
		local cfg = getCfg(kind)
		local dbc = cfg and cfg.dataBar or {}
		local def = DEFAULTS[kind] and DEFAULTS[kind].dataBar or {}
		local value = dbc.detachedMatchHealthWidth
		if value == nil then value = def.detachedMatchHealthWidth end
		if value == nil then value = true end
		return value ~= true
	end
	local function isDataBarNameTextEnabled()
		if not isDataBarEnabled() then return false end
		local cfg = getCfg(kind)
		local dbc = cfg and cfg.dataBar or {}
		local def = DEFAULTS[kind] and DEFAULTS[kind].dataBar or {}
		return (dbc.textLeft or def.textLeft) == "NAME" or (dbc.textCenter or def.textCenter) == "NAME" or (dbc.textRight or def.textRight) == "NAME"
	end
	local function isPortraitDetached()
		local cfg = getCfg(kind)
		return select(1, GF.ResolveGroupPortraitDetachedConfig(cfg, kind))
	end
	local function isDispelTintEnabled()
		local cfg = getCfg(kind)
		local sc = cfg and cfg.status or {}
		local dt = sc.dispelTint or {}
		local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
		if dt.enabled == nil then return def.enabled ~= false end
		return dt.enabled == true
	end
	local function isPowerTextEnabled(key, fallback) return getPowerTextMode(key, fallback) ~= "NONE" end
	local function anyHealthTextEnabled() return isHealthTextEnabled("textLeft") or isHealthTextEnabled("textCenter") or isHealthTextEnabled("textRight") end
	local function healthDelimiterCount() return maxDelimiterCount(getHealthTextMode("textLeft"), getHealthTextMode("textCenter"), getHealthTextMode("textRight")) end
	local function powerDelimiterCount() return maxDelimiterCount(getPowerTextMode("textLeft"), getPowerTextMode("textCenter"), getPowerTextMode("textRight")) end
	local function getHighlightCfg(key)
		local cfg = getCfg(kind)
		local hcfg = cfg and cfg[key] or {}
		local def = (DEFAULTS[kind] and DEFAULTS[kind][key]) or {}
		return hcfg, def
	end
	local function isHighlightEnabled(key)
		local hcfg, def = getHighlightCfg(key)
		local enabled = hcfg.enabled
		if enabled == nil then enabled = def.enabled end
		return enabled == true
	end
	local function getTargetHighlightLayerValue()
		local hcfg, def = getHighlightCfg("highlightTarget")
		local layer = tostring(hcfg.layer or def.layer or "ABOVE_BORDER"):upper()
		if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
		return layer
	end
	local function getTargetHighlightLayerLabel()
		local layer = getTargetHighlightLayerValue()
		for _, option in ipairs(targetHighlightLayerOptions) do
			if option.value == layer then return option.label end
		end
		return layer
	end
	local function targetHighlightLayerGenerator()
		return function(_, root, data)
			for _, option in ipairs(targetHighlightLayerOptions) do
				root:CreateRadio(option.label, function() return data.get and data.get() == option.value end, function()
					if data.set then data.set(nil, option.value) end
					data.customDefaultText = option.label
					if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
				end)
			end
		end
	end
	local function auraGrowthGenerator()
		return function(_, root, data)
			local opts = GFH.auraGrowthOptions or GFH.auraGrowthXOptions
			if type(opts) ~= "table" then return end
			for _, option in ipairs(opts) do
				local label = option.label or option.text or option.value or ""
				root:CreateRadio(label, function() return data.get and data.get() == option.value end, function()
					if data.set then data.set(nil, option.value) end
					if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
				end)
			end
		end
	end
	local function isExternalDRShown()
		local cfg = getCfg(kind)
		local ac = ensureAuraConfig(cfg)
		return ac.externals and ac.externals.showDR == true
	end
	local function isExternalGlowShown()
		local cfg = getCfg(kind)
		local ac = ensureAuraConfig(cfg)
		local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or EMPTY
		local enabled = ac.externals and ac.externals.glowEnabled
		if enabled == nil then enabled = def.glowEnabled == true end
		return enabled == true
	end
	local function getAuraTypeConfig(typeKey)
		local cfg = getCfg(kind)
		local ac = ensureAuraConfig(cfg)
		local typeCfg = ac and ac[typeKey] or EMPTY
		local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras[typeKey]) or EMPTY
		return typeCfg, def
	end
	local function isAuraCooldownTextEnabled(typeKey)
		local typeCfg, def = getAuraTypeConfig(typeKey)
		if not (typeCfg and typeCfg.enabled == true) then return false end
		if typeCfg.showCooldownText == nil then return def.showCooldownText ~= false end
		return typeCfg.showCooldownText ~= false
	end
	local function isAuraStackTextEnabled(typeKey)
		local typeCfg, def = getAuraTypeConfig(typeKey)
		if not (typeCfg and typeCfg.enabled == true) then return false end
		if typeCfg.showStacks == nil then return def.showStacks ~= false end
		return typeCfg.showStacks ~= false
	end
	local portraitSideOptions = {
		{ value = "LEFT", label = "Left", text = "Left" },
		{ value = "RIGHT", label = "Right", text = "Right" },
	}
	local portraitModeOptions = {
		{ value = "PORTRAIT", label = L["UFPortraitModePortrait"] or "Portrait", text = L["UFPortraitModePortrait"] or "Portrait" },
		{ value = "CLASS_ICON", label = L["UFPortraitModeClassIcon"] or "Class icon", text = L["UFPortraitModeClassIcon"] or "Class icon" },
	}
	local function isPortraitEnabled()
		local cfg = getCfg(kind)
		local pcfg = cfg and cfg.portrait or {}
		local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
		local enabled = pcfg.enabled
		if enabled == nil then enabled = defPortrait.enabled end
		return enabled == true
	end
	local function getPortraitSideValue()
		local cfg = getCfg(kind)
		local pcfg = cfg and cfg.portrait or {}
		local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
		local side = tostring(pcfg.side or defPortrait.side or "LEFT"):upper()
		if side ~= "RIGHT" then side = "LEFT" end
		return side
	end
	local function getPortraitModeValue()
		local cfg = getCfg(kind)
		local pcfg = cfg and cfg.portrait or {}
		local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
		local mode = tostring(pcfg.mode or defPortrait.mode or "PORTRAIT"):upper()
		if mode ~= "CLASS_ICON" then mode = "PORTRAIT" end
		return mode
	end
	local settings = {
		{
			name = SETTINGS or "Settings",
			kind = SettingType.Collapsible,
			id = "utility",
			defaultCollapsed = true,
		},
		{
			name = L["Copy settings"] or "Copy settings",
			kind = SettingType.Dropdown,
			field = "copyFrom",
			parentId = "utility",
			customDefaultText = L["UFSelectSource"] or "Select source",
			get = function() return nil end,
			set = function() end,
			generator = function(_, root, data)
				for _, option in ipairs(GF._buildGroupCopySourceOptions(kind)) do
					root:CreateRadio(option.label, function() return false end, function()
						GF._showGroupCopySettingsPopup(option, kind, editModeId)
						if data then data.customDefaultText = option.label end
						if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
					end)
				end
			end,
		},
		{
			name = L["UFAuraRenderer"] or "Aura renderer",
			kind = SettingType.Dropdown,
			field = "auraRenderer",
			parentId = "utility",
			values = auraRendererOptions,
			get = getAuraRendererValue,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.auras = cfg.auras or {}
				local previous = GF.GetGroupAuraRenderer(cfg, DEFAULTS[kind] or EMPTY)
				local nextRenderer = (value == "BLIZZARD") and "BLIZZARD" or "CUSTOM"
				cfg.auras.renderer = nextRenderer
				if previous == "BLIZZARD" and nextRenderer ~= "BLIZZARD" then GF.MarkBlizzardRendererRemovalReloadIfNeeded() end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "auraRenderer", cfg.auras.renderer, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
				GF:RefreshHealerBuffPlacement(kind)
				refreshAllAuras()
				refreshAllPrivateAuras()
			end,
		},
		{
			name = L["UFAuraRendererBlizzardTypes"] or "Rendered by Blizzard",
			kind = SettingType.MultiDropdown,
			field = "auraRendererBlizzardTypes",
			parentId = "utility",
			height = 150,
			values = blizzardAuraTypeOptions,
			isSelected = function(_, value)
				local cfg = getCfg(kind)
				return GF.IsBlizzardAuraRenderTypeEnabled(cfg, DEFAULTS[kind] or EMPTY, value)
			end,
			setSelected = function(_, value, state)
				local cfg = getCfg(kind)
				local selection = GF.EnsureBlizzardAuraRenderTypes(cfg)
				if not selection then return end
				local wasSelected = selection[value] == true
				if state then
					selection[value] = true
				else
					selection[value] = nil
				end
				if wasSelected and not state then GF.MarkBlizzardRendererRemovalReloadIfNeeded() end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "auraRendererBlizzardTypes", GF._sharedEdit.csm(selection), nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
				GF:RefreshHealerBuffPlacement(kind)
				refreshAllAuras()
				refreshAllPrivateAuras()
			end,
			isEnabled = isBlizzardRendererSelected,
		},
		{
			name = L["Icon size"] or "Icon size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "auraRendererBlizzardIconSize",
			parentId = "utility",
			minValue = 8,
			maxValue = 120,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				return GF.GetBlizzardAuraIconSize(cfg, DEFAULTS[kind] or EMPTY)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.auras = cfg.auras or {}
				cfg.auras.blizzardIconSize = clampNumber(value, 8, 120, GF.GetBlizzardAuraIconSize(cfg, DEFAULTS[kind] or EMPTY))
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "auraRendererBlizzardIconSize", cfg.auras.blizzardIconSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				refreshAllAuras()
				refreshAllPrivateAuras()
			end,
			isEnabled = isBlizzardRendererSelected,
			isShown = isBlizzardRendererSelected,
		},
		{
			name = L["Show cooldown text"] or "Show cooldown text",
			kind = SettingType.Checkbox,
			field = "auraRendererBlizzardCooldownText",
			parentId = "utility",
			get = function()
				local cfg = getCfg(kind)
				return GF.IsBlizzardAuraCooldownTextEnabled(cfg, DEFAULTS[kind] or EMPTY)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.auras = cfg.auras or {}
				cfg.auras.blizzardShowCooldownText = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "auraRendererBlizzardCooldownText", cfg.auras.blizzardShowCooldownText, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				refreshAllAuras()
				refreshAllPrivateAuras()
			end,
			isEnabled = isBlizzardRendererSelected,
			isShown = isBlizzardRendererSelected,
		},
		{
			name = L["UFAuraRendererBlizzardOrganization"] or "Aura organization",
			kind = SettingType.Dropdown,
			field = "auraRendererBlizzardOrganization",
			parentId = "utility",
			values = GF.blizzardAuraOrganizationOptions,
			customDefaultText = GF.DropdownOptionLabel(GF.blizzardAuraOrganizationOptions, GF.GetBlizzardAuraOrganization(getCfg(kind), DEFAULTS[kind] or EMPTY), L["UFAuraRendererBlizzardOrganizationLegacy"] or "Legacy"),
			get = function() return GF.GetBlizzardAuraOrganization(getCfg(kind), DEFAULTS[kind] or EMPTY) end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.auras = cfg.auras or {}
				cfg.auras.blizzardOrganizationType = GF.NormalizeBlizzardAuraOrganization(value) or GF.BLIZZARD_AURA_ORGANIZATION_LEGACY
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "auraRendererBlizzardOrganization", cfg.auras.blizzardOrganizationType, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				refreshAllAuras()
				refreshAllPrivateAuras()
			end,
			generator = GF.DropdownRadioGenerator(GF.blizzardAuraOrganizationOptions),
			isEnabled = isBlizzardRendererSelected,
			isShown = isBlizzardRendererSelected,
		},
		{
			name = L["UFAuraRendererBlizzardDispelMode"] or "Dispel indicator mode",
			kind = SettingType.Dropdown,
			field = "auraRendererBlizzardDispelMode",
			parentId = "utility",
			values = GF.blizzardDispelIndicatorModeOptions,
			customDefaultText = GF.DropdownOptionLabel(GF.blizzardDispelIndicatorModeOptions, GF.GetBlizzardDispelIndicatorMode(getCfg(kind), DEFAULTS[kind] or EMPTY), L["UFAuraRendererBlizzardDispelModeAll"] or "All"),
			get = function() return GF.GetBlizzardDispelIndicatorMode(getCfg(kind), DEFAULTS[kind] or EMPTY) end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.auras = cfg.auras or {}
				cfg.auras.blizzardDispelIndicatorMode = GF.NormalizeBlizzardDispelIndicatorMode(value) or GF.BLIZZARD_DISPEL_MODE_ALL
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "auraRendererBlizzardDispelMode", cfg.auras.blizzardDispelIndicatorMode, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				refreshAllAuras()
				refreshAllPrivateAuras()
			end,
			generator = GF.DropdownRadioGenerator(GF.blizzardDispelIndicatorModeOptions),
			isEnabled = function() return GF.IsBlizzardAuraRenderTypeEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY, "dispels") end,
			isShown = function() return GF.IsBlizzardAuraRenderTypeEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY, "dispels") end,
		},
		{
			name = L["Frame"] or "Frame",
			kind = SettingType.Collapsible,
			id = "frame",
			defaultCollapsed = true,
		},
		{
			name = widthLabel,
			kind = SettingType.Slider,
			allowInput = true,
			field = "width",
			minValue = 40,
			maxValue = 600,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].width) or 100,
			parentId = "frame",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.width or (DEFAULTS[kind] and DEFAULTS[kind].width) or 100
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local v = clampNumber(value, 40, 600, cfg.width or 100)
				cfg.width = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "width", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = heightLabel,
			kind = SettingType.Slider,
			allowInput = true,
			field = "height",
			minValue = 10,
			maxValue = 200,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].height) or 24,
			parentId = "frame",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.height or (DEFAULTS[kind] and DEFAULTS[kind].height) or 24
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local v = clampNumber(value, 10, 200, cfg.height or 24)
				cfg.height = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "height", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Anchor to"] or "Anchor to",
			kind = SettingType.Dropdown,
			field = "anchorTarget",
			parentId = "frame",
			height = 220,
			default = GF.GetGroupAnchorTargetValue(kind),
			get = function() return GF.GetGroupAnchorTargetValue(kind) end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local target = GF.ValidateGroupAnchorTarget(kind, value, cfg.relativeTo, false)
				if cfg.relativeTo == target then return end
				cfg.relativeTo = target
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "anchorTarget", target, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
				if EditMode and EditMode.RefreshFrame then EditMode:RefreshFrame(editModeId) end
			end,
			generator = function(_, root)
				for _, option in ipairs(GF.GetGroupAnchorTargetEntries(kind)) do
					root:CreateRadio(option.label, function() return GF.GetGroupAnchorTargetValue(kind) == option.value end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local target = GF.ValidateGroupAnchorTarget(kind, option.value, cfg.relativeTo, false)
						if cfg.relativeTo == target then return end
						cfg.relativeTo = target
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "anchorTarget", target, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						requestEditModeSettingsRefresh()
						if EditMode and EditMode.RefreshFrame then EditMode:RefreshFrame(editModeId) end
					end)
				end
			end,
			isShown = function() return kind == "party" or kind == "raid" end,
		},
		{
			name = L["Anchor point"] or "Anchor point",
			kind = SettingType.Dropdown,
			field = "point",
			parentId = "frame",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			default = (DEFAULTS[kind] and DEFAULTS[kind].point) or "CENTER",
			get = function()
				local cfg = getCfg(kind)
				return (cfg and cfg.point) or (DEFAULTS[kind] and DEFAULTS[kind].point) or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				cfg.point = tostring(value):upper()
				if not cfg.relativePoint or cfg.relativePoint == "" then cfg.relativePoint = cfg.point end
				if not cfg.relativeTo or cfg.relativeTo == "" then cfg.relativeTo = "UIParent" end
				if EditMode and EditMode.SetValue then
					GF.SyncGroupEditModePositionValues(kind, editModeId, cfg)
				end
				if EditMode and EditMode.RefreshFrame then
					EditMode:RefreshFrame(editModeId)
				end
				GF:ApplyHeaderAttributes(kind)
				if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
			end,
		},
		{
			name = L["Relative point"] or "Relative point",
			kind = SettingType.Dropdown,
			field = "relativePoint",
			parentId = "frame",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			default = (DEFAULTS[kind] and (DEFAULTS[kind].relativePoint or DEFAULTS[kind].point)) or "CENTER",
			get = function()
				local cfg = getCfg(kind)
				return (cfg and (cfg.relativePoint or cfg.point)) or (DEFAULTS[kind] and (DEFAULTS[kind].relativePoint or DEFAULTS[kind].point)) or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				cfg.relativePoint = tostring(value):upper()
				if not cfg.point or cfg.point == "" then cfg.point = cfg.relativePoint end
				if not cfg.relativeTo or cfg.relativeTo == "" then cfg.relativeTo = "UIParent" end
				if EditMode and EditMode.SetValue then
					GF.SyncGroupEditModePositionValues(kind, editModeId, cfg)
				end
				if EditMode and EditMode.RefreshFrame then
					EditMode:RefreshFrame(editModeId)
				end
				GF:ApplyHeaderAttributes(kind)
				if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
			end,
		},
		{
			name = L["Offset X"] or "Offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "x",
			minValue = -4000,
			maxValue = 4000,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].x) or 0,
			parentId = "frame",
			get = function()
				local cfg = getCfg(kind)
				return (cfg and cfg.x) or (DEFAULTS[kind] and DEFAULTS[kind].x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				if not cfg.relativeTo or cfg.relativeTo == "" then cfg.relativeTo = "UIParent" end
				local raw = clampNumber(value, -4000, 4000, cfg.x or 0)
				cfg.x = raw
				if EditMode and EditMode.SetValue then GF.SyncGroupEditModePositionValues(kind, editModeId, cfg) end
				if EditMode and EditMode.RefreshFrame then
					EditMode:RefreshFrame(editModeId)
				end
				GF:ApplyHeaderAttributes(kind)
				if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
			end,
		},
		{
			name = L["Offset Y"] or "Offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "y",
			minValue = -4000,
			maxValue = 4000,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].y) or 0,
			parentId = "frame",
			get = function()
				local cfg = getCfg(kind)
				return (cfg and cfg.y) or (DEFAULTS[kind] and DEFAULTS[kind].y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				if not cfg.relativeTo or cfg.relativeTo == "" then cfg.relativeTo = "UIParent" end
				local raw = clampNumber(value, -4000, 4000, cfg.y or 0)
				cfg.y = raw
				if EditMode and EditMode.SetValue then GF.SyncGroupEditModePositionValues(kind, editModeId, cfg) end
				if EditMode and EditMode.RefreshFrame then
					EditMode:RefreshFrame(editModeId)
				end
				GF:ApplyHeaderAttributes(kind)
				if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
			end,
		},
		{
			name = L["Power height"] or "Power height",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerHeight",
			minValue = 0,
			maxValue = 1000,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].powerHeight) or 6,
			parentId = "frame",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.powerHeight or (DEFAULTS[kind] and DEFAULTS[kind].powerHeight) or 6
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local v = clampNumber(value, 0, 50, cfg.powerHeight or 6)
				cfg.powerHeight = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerHeight", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Hide in client scenes"] or "Hide in client scenes",
			kind = SettingType.Checkbox,
			field = "hideInClientScene",
			parentId = "frame",
			default = (DEFAULTS[kind] and DEFAULTS[kind].hideInClientScene) ~= false,
			get = function()
				local cfg = getCfg(kind)
				local value = cfg and cfg.hideInClientScene
				if value == nil then value = DEFAULTS[kind] and DEFAULTS[kind].hideInClientScene end
				if value == nil then value = true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.hideInClientScene = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hideInClientScene", cfg.hideInClientScene, nil, true) end
				GF:RefreshClientSceneVisibility()
			end,
		},
		{
			name = L["Tooltip"] or "Tooltip",
			kind = SettingType.Dropdown,
			field = "tooltipMode",
			parentId = "frame",
			default = (DEFAULTS[kind] and DEFAULTS[kind].tooltip and DEFAULTS[kind].tooltip.mode) or "OFF",
			customDefaultText = getTooltipModeLabel(),
			get = function() return getTooltipModeValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				cfg.tooltip = cfg.tooltip or {}
				cfg.tooltip.mode = tostring(value):upper()
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "tooltipMode", cfg.tooltip.mode, nil, true) end
			end,
			generator = tooltipModeGenerator(),
		},
		{
			name = L["UFTooltipUseEditMode"] or "Use Edit Mode tooltip position",
			kind = SettingType.Checkbox,
			field = "tooltipUseEditMode",
			parentId = "frame",
			default = (DEFAULTS[kind] and DEFAULTS[kind].tooltip and DEFAULTS[kind].tooltip.useEditMode) == true,
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.tooltip or {}
				local defTooltip = (DEFAULTS[kind] and DEFAULTS[kind].tooltip) or {}
				if tc.useEditMode == nil then return defTooltip.useEditMode == true end
				return tc.useEditMode == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.tooltip = cfg.tooltip or {}
				cfg.tooltip.useEditMode = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "tooltipUseEditMode", cfg.tooltip.useEditMode, nil, true) end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.tooltip or {}
				local mode = tc.mode or (DEFAULTS[kind] and DEFAULTS[kind].tooltip and DEFAULTS[kind].tooltip.mode) or "OFF"
				return tostring(mode):upper() ~= "OFF"
			end,
		},
		{
			name = L["Tooltip modifier"] or "Tooltip modifier",
			kind = SettingType.Dropdown,
			field = "tooltipModifier",
			parentId = "frame",
			default = (DEFAULTS[kind] and DEFAULTS[kind].tooltip and DEFAULTS[kind].tooltip.modifier) or "ALT",
			customDefaultText = getTooltipModifierLabel(),
			get = function() return getTooltipModifierValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				cfg.tooltip = cfg.tooltip or {}
				cfg.tooltip.modifier = tostring(value):upper()
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "tooltipModifier", cfg.tooltip.modifier, nil, true) end
			end,
			generator = tooltipModifierGenerator(),
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.tooltip or {}
				local mode = tc.mode or (DEFAULTS[kind] and DEFAULTS[kind].tooltip and DEFAULTS[kind].tooltip.mode) or "OFF"
				return mode == "MODIFIER"
			end,
		},
		{
			name = L["Show tooltip for auras"] or "Show tooltip for auras",
			kind = SettingType.Checkbox,
			field = "tooltipAuras",
			parentId = "frame",
			default = false,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.showTooltip == true and ac.debuff.showTooltip == true and ac.externals.showTooltip == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local enabled = value and true or false
				ac.buff.showTooltip = enabled
				ac.debuff.showTooltip = enabled
				ac.externals.showTooltip = enabled
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "tooltipAuras", enabled, nil, true) end
				refreshAllAuras()
			end,
		},
		{
			name = L["Show tooltip for private auras"] or "Show tooltip for private auras",
			kind = SettingType.Checkbox,
			field = "privateAurasShowTooltip",
			parentId = "frame",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local defValue = defPrivateAuras.showTooltip == true
				if pcfg.showTooltip == nil then return defValue end
				return pcfg.showTooltip == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.showTooltip = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasShowTooltip", pcfg.showTooltip, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Layout"] or "Layout",
			kind = SettingType.Collapsible,
			id = "layout",
			defaultCollapsed = true,
		},
		{
			name = L["Spacing"] or "Spacing",
			kind = SettingType.Slider,
			allowInput = true,
			field = "spacing",
			minValue = 0,
			maxValue = GROUP_FRAME_MAX_SPACING,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].spacing) or 0,
			parentId = "layout",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.spacing or (DEFAULTS[kind] and DEFAULTS[kind].spacing) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local v = clampNumber(value, 0, GROUP_FRAME_MAX_SPACING, cfg.spacing or 0)
				cfg.spacing = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "spacing", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Growth"] or "Growth",
			kind = SettingType.Dropdown,
			field = "growth",
			parentId = "layout",
			get = function()
				local cfg = getCfg(kind)
				local raw = (cfg and cfg.growth) or (DEFAULTS[kind] and DEFAULTS[kind].growth) or "DOWN"
				local mode, base = GF.ResolveUnitGrowthDirection(raw, "DOWN")
				if not GF.SupportsCenterGrowth(kind) and (mode == "CENTER_HORIZONTAL" or mode == "CENTER_VERTICAL") then return base end
				return mode
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				local mode, baseGrowth = GF.ResolveUnitGrowthDirection(value, cfg.growth or "DOWN")
				if not GF.SupportsCenterGrowth(kind) and (mode == "CENTER_HORIZONTAL" or mode == "CENTER_VERTICAL") then mode = baseGrowth end
				cfg.growth = mode
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "growth", cfg.growth, nil, true) end
				if raidKind then
					local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
					if GFH.ResolveGroupGrowthDirection then
						cfg.groupGrowth = GFH.ResolveGroupGrowthDirection(cfg.groupGrowth, baseGrowth, defaultGroupGrowth)
					else
						cfg.groupGrowth = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(cfg.groupGrowth, nil))
							or ((baseGrowth == "RIGHT" or baseGrowth == "LEFT") and "DOWN" or "RIGHT")
					end
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupGrowth", cfg.groupGrowth, nil, true) end
				end
				GF:ApplyHeaderAttributes(kind)
				if kind == "party" then GF:RefreshCustomSortNameList(kind) end
			end,
			generator = function(_, root)
				local options = {
					{ value = "DOWN", label = HUD_EDIT_MODE_SETTING_BAGS_DIRECTION_DOWN },
					{ value = "RIGHT", label = HUD_EDIT_MODE_SETTING_ENCOUNTER_EVENTS_ICON_DIRECTION_RIGHT },
					{ value = "UP", label = HUD_EDIT_MODE_SETTING_BAGS_DIRECTION_UP },
					{ value = "LEFT", label = HUD_EDIT_MODE_SETTING_ENCOUNTER_EVENTS_ICON_DIRECTION_LEFT },
				}
				if GF.SupportsCenterGrowth(kind) then
					options[#options + 1] = { value = "CENTER_VERTICAL", label = L["Center vertical"] or "Center vertical" }
					options[#options + 1] = { value = "CENTER_HORIZONTAL", label = L["Center horizontal"] or "Center horizontal" }
				end
				for _, option in ipairs(options) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local mode, base = GF.ResolveUnitGrowthDirection(cfg and cfg.growth, "DOWN")
						if not GF.SupportsCenterGrowth(kind) and (mode == "CENTER_HORIZONTAL" or mode == "CENTER_VERTICAL") then mode = base end
						return mode == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local mode, baseGrowth = GF.ResolveUnitGrowthDirection(option.value, cfg.growth or "DOWN")
						if not GF.SupportsCenterGrowth(kind) and (mode == "CENTER_HORIZONTAL" or mode == "CENTER_VERTICAL") then mode = baseGrowth end
						cfg.growth = mode
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "growth", cfg.growth, nil, true) end
						if raidKind then
							local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
							if GFH.ResolveGroupGrowthDirection then
								cfg.groupGrowth = GFH.ResolveGroupGrowthDirection(cfg.groupGrowth, baseGrowth, defaultGroupGrowth)
							else
								cfg.groupGrowth = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(cfg.groupGrowth, nil))
									or ((baseGrowth == "RIGHT" or baseGrowth == "LEFT") and "DOWN" or "RIGHT")
							end
							if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupGrowth", cfg.groupGrowth, nil, true) end
						end
						GF:ApplyHeaderAttributes(kind)
						if kind == "party" then GF:RefreshCustomSortNameList(kind) end
					end)
				end
			end,
		},
		{
			name = L["Group Growth"] or "Group Growth",
			kind = SettingType.Dropdown,
			field = "groupGrowth",
			parentId = "layout",
			default = (DEFAULTS.raid and DEFAULTS.raid.groupGrowth) or "DOWN",
			get = function()
				if not raidKind then return "DOWN" end
				local cfg = getCfg(kind)
				local _, growth = GF.ResolveUnitGrowthDirection(cfg and cfg.growth, "DOWN")
				local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
				if GFH.ResolveGroupGrowthDirection then return GFH.ResolveGroupGrowthDirection(cfg and cfg.groupGrowth, growth, defaultGroupGrowth) end
				return (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(cfg and cfg.groupGrowth, nil)) or ((growth == "RIGHT" or growth == "LEFT") and "DOWN" or "RIGHT")
			end,
			set = function(_, value)
				if not raidKind then return end
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				local _, growth = GF.ResolveUnitGrowthDirection(cfg.growth, "DOWN")
				local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
				if GFH.ResolveGroupGrowthDirection then
					cfg.groupGrowth = GFH.ResolveGroupGrowthDirection(value, growth, defaultGroupGrowth)
				else
					cfg.groupGrowth = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(value, nil)) or ((growth == "RIGHT" or growth == "LEFT") and "DOWN" or "RIGHT")
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupGrowth", cfg.groupGrowth, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
			end,
			isShown = function() return raidKind end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return raidKind and not GF.IsCenterGrowthMode(kind, cfg)
			end,
			generator = function(_, root)
				local cfg = getCfg(kind)
				local _, growth = GF.ResolveUnitGrowthDirection(cfg and cfg.growth, "DOWN")
				local optionA, optionB
				if GFH.GetAllowedGroupGrowthDirections then
					optionA, optionB = GFH.GetAllowedGroupGrowthDirections(growth)
				end
				if not optionA or not optionB then
					if growth == "RIGHT" or growth == "LEFT" then
						optionA, optionB = "DOWN", "UP"
					else
						optionA, optionB = "RIGHT", "LEFT"
					end
				end
				local function toGrowthLabel(value)
					if value == "UP" then return HUD_EDIT_MODE_SETTING_BAGS_DIRECTION_UP end
					if value == "DOWN" then return HUD_EDIT_MODE_SETTING_BAGS_DIRECTION_DOWN end
					if value == "LEFT" then return HUD_EDIT_MODE_SETTING_ENCOUNTER_EVENTS_ICON_DIRECTION_LEFT end
					if value == "RIGHT" then return HUD_EDIT_MODE_SETTING_ENCOUNTER_EVENTS_ICON_DIRECTION_RIGHT end
					return value
				end
				local options = {
					{ value = optionA, label = toGrowthLabel(optionA) },
					{ value = optionB, label = toGrowthLabel(optionB) },
				}
				for _, option in ipairs(options) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local _, growth = GF.ResolveUnitGrowthDirection(cfg and cfg.growth, "DOWN")
						local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
						local current
						if GFH.ResolveGroupGrowthDirection then
							current = GFH.ResolveGroupGrowthDirection(cfg and cfg.groupGrowth, growth, defaultGroupGrowth)
						else
							current = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(cfg and cfg.groupGrowth, nil)) or ((growth == "RIGHT" or growth == "LEFT") and "DOWN" or "RIGHT")
						end
						return current == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
						local _, growth = GF.ResolveUnitGrowthDirection(cfg.growth, "DOWN")
						if GFH.ResolveGroupGrowthDirection then
							cfg.groupGrowth = GFH.ResolveGroupGrowthDirection(option.value, growth, defaultGroupGrowth)
						else
							cfg.groupGrowth = option.value
						end
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupGrowth", cfg.groupGrowth, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
					end)
				end
			end,
			},
			{
				name = L["UFGroupGroupsPerRow"] or "Groups per row",
				kind = SettingType.Slider,
				allowInput = true,
				field = "groupsPerRow",
				parentId = "layout",
				minValue = 2,
				maxValue = 8,
				valueStep = 1,
				default = (DEFAULTS.raid and DEFAULTS.raid.groupsPerRow) or 8,
				get = function()
					if not raidKind then return (DEFAULTS.raid and DEFAULTS.raid.groupsPerRow) or 8 end
					local cfg = getCfg(kind)
					return GF.NormalizeRaidGroupsPerRow(cfg and cfg.groupsPerRow, DEFAULTS.raid and DEFAULTS.raid.groupsPerRow)
				end,
				set = function(_, value)
					if not raidKind then return end
					local cfg = getCfg(kind)
					if not cfg then return end
					local v = GF.NormalizeRaidGroupsPerRow(value, cfg.groupsPerRow or (DEFAULTS.raid and DEFAULTS.raid.groupsPerRow))
					cfg.groupsPerRow = v
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupsPerRow", v, nil, true) end
					GF:ApplyHeaderAttributes(kind)
					if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
					GF:RefreshGroupIndicators()
				end,
				isShown = function() return isRaidGroupFilterShown() end,
				isEnabled = function() return raidKind and isRaidGroupFilterShown() end,
			},
			{
				name = L["Frame texture"] or "Frame texture",
				kind = SettingType.Dropdown,
				field = "barTexture",
				parentId = "layout",
				height = 180,
				get = function()
				local cfg = getCfg(kind)
				local tex = cfg and cfg.barTexture
				if not tex or tex == "" or tex == BAR_TEX_INHERIT then return BAR_TEX_INHERIT end
				return tex
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				if value == BAR_TEX_INHERIT then
					cfg.barTexture = BAR_TEX_INHERIT
				else
					cfg.barTexture = value
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "barTexture", cfg.barTexture or BAR_TEX_INHERIT, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				root:CreateRadio(L["Use health/power textures"] or "Use health/power textures", function()
					local cfg = getCfg(kind)
					return usesPerBarTextureSelection(cfg)
				end, function()
					local cfg = getCfg(kind)
					if not cfg then return end
					cfg.barTexture = BAR_TEX_INHERIT
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "barTexture", BAR_TEX_INHERIT, nil, true) end
					GF:ApplyHeaderAttributes(kind)
				end)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						return (cfg and cfg.barTexture) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.barTexture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "barTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = EMBLEM_BORDER,
			kind = SettingType.Collapsible,
			id = "border",
			defaultCollapsed = true,
		},
		{
			name = L["Show border"] or "Show border",
			kind = SettingType.Checkbox,
			field = "borderEnabled",
			parentId = "border",
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				if bc.enabled == nil then return (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.enabled) ~= false end
				return bc.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.border = cfg.border or {}
				cfg.border.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderEnabled", cfg.border.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = EMBLEM_BORDER_COLOR,
			kind = SettingType.Color,
			field = "borderColor",
			parentId = "border",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.color) or { 0, 0, 0, 0.8 },
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.color) or { 0, 0, 0, 0.8 }
				local r, g, b, a = unpackColor(bc.color, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.border = cfg.border or {}
				cfg.border.color = { value.r or 0, value.g or 0, value.b or 0, value.a or 0.8 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderColor", cfg.border.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.enabled ~= false
			end,
		},
		{
			name = L["Border texture"] or "Border texture",
			kind = SettingType.Dropdown,
			field = "borderTexture",
			parentId = "border",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.texture or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.texture) or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.border = cfg.border or {}
				cfg.border.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderTexture", cfg.border.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(borderOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local bc = cfg and cfg.border or {}
						return (bc.texture or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.texture) or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.border = cfg.border or {}
						cfg.border.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.enabled ~= false
			end,
		},
		{
			name = L["Border strata"] or "Border strata",
			kind = SettingType.Dropdown,
			field = "borderStrata",
			parentId = "border",
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				local value = bc.strata
				if value == nil then value = (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.strata) or "" end
				value = tostring(value or ""):upper()
				if value == "DEFAULT" then value = "" end
				if
					value ~= ""
					and value ~= "BACKGROUND"
					and value ~= "LOW"
					and value ~= "MEDIUM"
					and value ~= "HIGH"
					and value ~= "DIALOG"
					and value ~= "FULLSCREEN"
					and value ~= "FULLSCREEN_DIALOG"
					and value ~= "TOOLTIP"
				then
					value = ""
				end
				return value
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.border = cfg.border or {}
				local strata = tostring(value or ""):upper()
				if strata == "DEFAULT" then strata = "" end
				if
					strata ~= ""
					and strata ~= "BACKGROUND"
					and strata ~= "LOW"
					and strata ~= "MEDIUM"
					and strata ~= "HIGH"
					and strata ~= "DIALOG"
					and strata ~= "FULLSCREEN"
					and strata ~= "FULLSCREEN_DIALOG"
					and strata ~= "TOOLTIP"
				then
					strata = ""
				end
				cfg.border.strata = (strata ~= "") and strata or nil
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderStrata", cfg.border.strata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs({
					{ value = "", label = DEFAULT or "Default" },
					{ value = "BACKGROUND", label = "BACKGROUND" },
					{ value = "LOW", label = "LOW" },
					{ value = "MEDIUM", label = "MEDIUM" },
					{ value = "HIGH", label = "HIGH" },
					{ value = "DIALOG", label = "DIALOG" },
					{ value = "FULLSCREEN", label = "FULLSCREEN" },
					{ value = "FULLSCREEN_DIALOG", label = "FULLSCREEN_DIALOG" },
					{ value = "TOOLTIP", label = "TOOLTIP" },
				}) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local bc = cfg and cfg.border or {}
						local value = bc.strata
						if value == nil then value = (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.strata) or "" end
						value = tostring(value or ""):upper()
						if value == "DEFAULT" then value = "" end
						if
							value ~= ""
							and value ~= "BACKGROUND"
							and value ~= "LOW"
							and value ~= "MEDIUM"
							and value ~= "HIGH"
							and value ~= "DIALOG"
							and value ~= "FULLSCREEN"
							and value ~= "FULLSCREEN_DIALOG"
							and value ~= "TOOLTIP"
						then
							value = ""
						end
						return value == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.border = cfg.border or {}
						local strata = tostring(option.value or ""):upper()
						if strata == "DEFAULT" then strata = "" end
						if
							strata ~= ""
							and strata ~= "BACKGROUND"
							and strata ~= "LOW"
							and strata ~= "MEDIUM"
							and strata ~= "HIGH"
							and strata ~= "DIALOG"
							and strata ~= "FULLSCREEN"
							and strata ~= "FULLSCREEN_DIALOG"
							and strata ~= "TOOLTIP"
						then
							strata = ""
						end
						cfg.border.strata = (strata ~= "") and strata or nil
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderStrata", cfg.border.strata or "", nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.enabled ~= false
			end,
		},
		{
			name = L["Border frame level offset"] or "Border frame level offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "borderFrameLevelOffset",
			parentId = "border",
			minValue = -20,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				local value = bc.frameLevelOffset
				if value == nil then value = (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.frameLevelOffset) end
				value = clampNumber(value, -20, 1000, 3)
				return floor(value + (value >= 0 and 0.5 or -0.5))
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.border = cfg.border or {}
				local levelOffset = clampNumber(value, -20, 1000, cfg.border.frameLevelOffset or 3)
				levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
				cfg.border.frameLevelOffset = levelOffset
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderFrameLevelOffset", cfg.border.frameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.enabled ~= false
			end,
		},
		{
			name = L["Border size"] or "Border size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "borderSize",
			parentId = "border",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.edgeSize or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.edgeSize) or 1
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.border = cfg.border or {}
				cfg.border.edgeSize = clampNumber(value, 1, 64, cfg.border.edgeSize or 1)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderSize", cfg.border.edgeSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.enabled ~= false
			end,
		},
		{
			name = L["Border offset"] or "Border offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "borderOffset",
			parentId = "border",
			minValue = 0,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				if bc.offset == nil and bc.inset == nil then return bc.edgeSize or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.edgeSize) or 1 end
				return bc.offset or bc.inset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.border = cfg.border or {}
				cfg.border.offset = clampNumber(value, 0, 64, cfg.border.offset or cfg.border.inset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "borderOffset", cfg.border.offset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				return bc.enabled ~= false
			end,
		},
		{
			name = string.format("%s %s", PARTY or "Party", EMBLEM_BORDER or "Border"),
			kind = SettingType.Collapsible,
			id = "partyGroupBorder",
			defaultCollapsed = true,
			isShown = function() return kind == "party" end,
		},
		{
			name = L["Show border"] or "Show border",
			kind = SettingType.Checkbox,
			field = "groupBorderEnabled",
			parentId = "partyGroupBorder",
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				local def = DEFAULTS.party and DEFAULTS.party.groupBorder or {}
				if bc.enabled == nil then return def.enabled == true end
				return bc.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupBorder = cfg.groupBorder or {}
				cfg.groupBorder.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupBorderEnabled", cfg.groupBorder.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" end,
		},
		{
			name = EMBLEM_BORDER_COLOR,
			kind = SettingType.Color,
			field = "groupBorderColor",
			parentId = "partyGroupBorder",
			hasOpacity = true,
			default = (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.color) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				local def = (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.color) or { 1, 1, 1, 1 }
				local r, g, b, a = unpackColor(bc.color, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.groupBorder = cfg.groupBorder or {}
				cfg.groupBorder.color = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupBorderColor", cfg.groupBorder.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				return bc.enabled == true
			end,
		},
		{
			name = L["Border texture"] or "Border texture",
			kind = SettingType.Dropdown,
			field = "groupBorderTexture",
			parentId = "partyGroupBorder",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				return bc.texture or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.texture) or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupBorder = cfg.groupBorder or {}
				cfg.groupBorder.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupBorderTexture", cfg.groupBorder.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(borderOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local bc = cfg and cfg.groupBorder or {}
						return (bc.texture or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.texture) or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.groupBorder = cfg.groupBorder or {}
						cfg.groupBorder.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupBorderTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isShown = function() return kind == "party" end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				return bc.enabled == true
			end,
		},
		{
			name = L["Border size"] or "Border size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupBorderSize",
			parentId = "partyGroupBorder",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				return bc.edgeSize or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.edgeSize) or 1
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupBorder = cfg.groupBorder or {}
				cfg.groupBorder.edgeSize = clampNumber(value, 1, 64, cfg.groupBorder.edgeSize or 1)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupBorderSize", cfg.groupBorder.edgeSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				return bc.enabled == true
			end,
		},
		{
			name = L["Border offset"] or "Border offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupBorderOffset",
			parentId = "partyGroupBorder",
			minValue = 0,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				if bc.offset == nil and bc.inset == nil then return bc.edgeSize or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.edgeSize) or 1 end
				return bc.offset or bc.inset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupBorder = cfg.groupBorder or {}
				cfg.groupBorder.offset = clampNumber(value, 0, 64, cfg.groupBorder.offset or cfg.groupBorder.inset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupBorderOffset", cfg.groupBorder.offset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local bc = cfg and cfg.groupBorder or {}
				return bc.enabled == true
			end,
		},
		{
			name = L["Hover highlight"] or "Hover highlight",
			kind = SettingType.Collapsible,
			id = "hoverHighlight",
			defaultCollapsed = true,
		},
		{
			name = L["Enable hover highlight"] or "Enable hover highlight",
			kind = SettingType.Checkbox,
			field = "hoverHighlightEnabled",
			parentId = "hoverHighlight",
			get = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				if hcfg.enabled == nil then return def.enabled == true end
				return hcfg.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightHover = cfg.highlightHover or {}
				cfg.highlightHover.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightEnabled", cfg.highlightHover.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = COLOR,
			kind = SettingType.Color,
			field = "hoverHighlightColor",
			parentId = "hoverHighlight",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].highlightHover and DEFAULTS[kind].highlightHover.color) or GFH.COLOR_WHITE_90,
			get = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				local r, g, b, a = unpackColor(hcfg.color, def.color or GFH.COLOR_WHITE_90)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.highlightHover = cfg.highlightHover or {}
				cfg.highlightHover.color = { value.r or 1, value.g or 1, value.b or 1, value.a or 0.9 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightColor", cfg.highlightHover.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightHover") end,
		},
		{
			name = L["Texture"] or "Texture",
			kind = SettingType.Dropdown,
			field = "hoverHighlightTexture",
			parentId = "hoverHighlight",
			height = 180,
			get = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				return hcfg.texture or def.texture or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightHover = cfg.highlightHover or {}
				cfg.highlightHover.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightTexture", cfg.highlightHover.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(borderOptions()) do
					root:CreateRadio(option.label, function()
						local hcfg, def = getHighlightCfg("highlightHover")
						return (hcfg.texture or def.texture or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.highlightHover = cfg.highlightHover or {}
						cfg.highlightHover.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isHighlightEnabled("highlightHover") end,
		},
		{
			name = L["Frame strata"] or "Frame strata",
			kind = SettingType.Dropdown,
			field = "hoverHighlightStrata",
			parentId = "hoverHighlight",
			get = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				return GF.NormalizeFrameStrataToken(hcfg.strata) or GF.NormalizeFrameStrataToken(def.strata) or ""
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightHover = cfg.highlightHover or {}
				cfg.highlightHover.strata = GF.NormalizeFrameStrataToken(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightStrata", cfg.highlightHover.strata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			getValueText = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				return GF.DropdownOptionLabel(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT, GF.NormalizeFrameStrataToken(hcfg.strata) or GF.NormalizeFrameStrataToken(def.strata) or "", DEFAULT or "Default")
			end,
			generator = GF.DropdownRadioGenerator(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT),
			isEnabled = function() return isHighlightEnabled("highlightHover") end,
		},
		{
			name = "Frame level",
			kind = SettingType.Slider,
			allowInput = true,
			field = "hoverHighlightFrameLevelOffset",
			parentId = "hoverHighlight",
			minValue = -20,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				local value = hcfg.frameLevelOffset
				if value == nil then value = def.frameLevelOffset end
				if value == nil then value = 4 end
				value = clampNumber(value, -20, 1000, 4)
				return floor(value + (value >= 0 and 0.5 or -0.5))
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightHover = cfg.highlightHover or {}
				local levelOffset = clampNumber(value, -20, 1000, cfg.highlightHover.frameLevelOffset or 4)
				levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
				cfg.highlightHover.frameLevelOffset = levelOffset
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightFrameLevelOffset", cfg.highlightHover.frameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightHover") end,
		},
		{
			name = L["Size"] or "Size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "hoverHighlightSize",
			parentId = "hoverHighlight",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				return hcfg.size or def.size or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightHover = cfg.highlightHover or {}
				cfg.highlightHover.size = clampNumber(value, 1, 64, cfg.highlightHover.size or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightSize", cfg.highlightHover.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightHover") end,
		},
		{
			name = L["Offset"] or "Offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "hoverHighlightOffset",
			parentId = "hoverHighlight",
			minValue = -64,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local hcfg, def = getHighlightCfg("highlightHover")
				return hcfg.offset or def.offset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightHover = cfg.highlightHover or {}
				cfg.highlightHover.offset = clampNumber(value, -64, 64, cfg.highlightHover.offset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hoverHighlightOffset", cfg.highlightHover.offset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightHover") end,
		},
		{
			name = L["Aggro highlight"] or "Aggro highlight",
			kind = SettingType.Collapsible,
			id = "aggroHighlight",
			defaultCollapsed = true,
		},
		{
			name = L["Enable aggro highlight"] or "Enable aggro highlight",
			kind = SettingType.Checkbox,
			field = "aggroHighlightEnabled",
			parentId = "aggroHighlight",
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				if hcfg.enabled == nil then return def.enabled == true end
				return hcfg.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				cfg.highlightAggro.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightEnabled", cfg.highlightAggro.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = COLOR,
			kind = SettingType.Color,
			field = "aggroHighlightColor",
			parentId = "aggroHighlight",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].highlightAggro and DEFAULTS[kind].highlightAggro.color) or { 1, 0.55, 0, 1 },
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				local r, g, b, a = unpackColor(hcfg.color, def.color or { 1, 0.55, 0, 1 })
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				cfg.highlightAggro.color = { value.r or 1, value.g or 0.55, value.b or 0, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightColor", cfg.highlightAggro.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightAggro") end,
		},
		{
			name = MODE,
			kind = SettingType.Dropdown,
			field = "aggroHighlightMode",
			parentId = "aggroHighlight",
			default = (DEFAULTS[kind] and DEFAULTS[kind].highlightAggro and DEFAULTS[kind].highlightAggro.mode) or "ALL",
			customDefaultText = (function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				local mode = GF.NormalizeAggroHighlightMode(hcfg.mode or def.mode or "ALL")
				if mode == "NON_TANKS" then return L["UFAggroHighlightModeNonTanks"] or "Only non-tanks" end
				return L["All"] or "All"
			end)(),
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				return GF.NormalizeAggroHighlightMode(hcfg.mode or def.mode or "ALL")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				cfg.highlightAggro.mode = GF.NormalizeAggroHighlightMode(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightMode", cfg.highlightAggro.mode, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				root:CreateRadio(L["All"] or "All", function()
					local hcfg, def = getHighlightCfg("highlightAggro")
					return GF.NormalizeAggroHighlightMode(hcfg.mode or def.mode or "ALL") == "ALL"
				end, function()
					local cfg = getCfg(kind)
					if not cfg then return end
					cfg.highlightAggro = cfg.highlightAggro or {}
					cfg.highlightAggro.mode = "ALL"
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightMode", cfg.highlightAggro.mode, nil, true) end
					GF:ApplyHeaderAttributes(kind)
				end)
				root:CreateRadio(L["UFAggroHighlightModeNonTanks"] or "Only non-tanks", function()
					local hcfg, def = getHighlightCfg("highlightAggro")
					return GF.NormalizeAggroHighlightMode(hcfg.mode or def.mode or "ALL") == "NON_TANKS"
				end, function()
					local cfg = getCfg(kind)
					if not cfg then return end
					cfg.highlightAggro = cfg.highlightAggro or {}
					cfg.highlightAggro.mode = "NON_TANKS"
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightMode", cfg.highlightAggro.mode, nil, true) end
					GF:ApplyHeaderAttributes(kind)
				end)
			end,
			isEnabled = function() return isHighlightEnabled("highlightAggro") end,
		},
		{
			name = L["Sample"] or "Sample",
			kind = SettingType.Checkbox,
			field = "aggroHighlightSample",
			parentId = "aggroHighlight",
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				if hcfg.sample == nil then return def.sample == true end
				return hcfg.sample == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				cfg.highlightAggro.sample = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightSample", cfg.highlightAggro.sample, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightAggro") end,
		},
		{
			name = L["Texture"] or "Texture",
			kind = SettingType.Dropdown,
			field = "aggroHighlightTexture",
			parentId = "aggroHighlight",
			height = 180,
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				return hcfg.texture or def.texture or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				cfg.highlightAggro.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightTexture", cfg.highlightAggro.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(borderOptions()) do
					root:CreateRadio(option.label, function()
						local hcfg, def = getHighlightCfg("highlightAggro")
						return (hcfg.texture or def.texture or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.highlightAggro = cfg.highlightAggro or {}
						cfg.highlightAggro.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isHighlightEnabled("highlightAggro") end,
		},
		{
			name = L["UFTargetHighlightLayer"] or "Layer",
			kind = SettingType.Dropdown,
			field = "aggroHighlightLayer",
			parentId = "aggroHighlight",
			default = (DEFAULTS[kind] and DEFAULTS[kind].highlightAggro and DEFAULTS[kind].highlightAggro.layer) or "ABOVE_BORDER",
			customDefaultText = (function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				local layer = tostring(hcfg.layer or def.layer or "ABOVE_BORDER"):upper()
				if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
				for _, option in ipairs(targetHighlightLayerOptions) do
					if option.value == layer then return option.label end
				end
				return layer
			end)(),
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				local layer = tostring(hcfg.layer or def.layer or "ABOVE_BORDER"):upper()
				if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
				return layer
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				local layer = tostring(value or "ABOVE_BORDER"):upper()
				if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
				cfg.highlightAggro.layer = layer
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightLayer", cfg.highlightAggro.layer, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = targetHighlightLayerGenerator(),
			isEnabled = function() return isHighlightEnabled("highlightAggro") end,
		},
		{
			name = L["Size"] or "Size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "aggroHighlightSize",
			parentId = "aggroHighlight",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				return hcfg.size or def.size or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				cfg.highlightAggro.size = clampNumber(value, 1, 64, cfg.highlightAggro.size or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightSize", cfg.highlightAggro.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightAggro") end,
		},
		{
			name = L["Offset"] or "Offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "aggroHighlightOffset",
			parentId = "aggroHighlight",
			minValue = -64,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local hcfg, def = getHighlightCfg("highlightAggro")
				return hcfg.offset or def.offset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightAggro = cfg.highlightAggro or {}
				cfg.highlightAggro.offset = clampNumber(value, -64, 64, cfg.highlightAggro.offset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "aggroHighlightOffset", cfg.highlightAggro.offset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightAggro") end,
		},
		{
			name = L["Target highlight"] or "Target highlight",
			kind = SettingType.Collapsible,
			id = "targetHighlight",
			defaultCollapsed = true,
		},
		{
			name = L["Enable target highlight"] or "Enable target highlight",
			kind = SettingType.Checkbox,
			field = "targetHighlightEnabled",
			parentId = "targetHighlight",
			get = function()
				local hcfg, def = getHighlightCfg("highlightTarget")
				if hcfg.enabled == nil then return def.enabled == true end
				return hcfg.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightTarget = cfg.highlightTarget or {}
				cfg.highlightTarget.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "targetHighlightEnabled", cfg.highlightTarget.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = COLOR,
			kind = SettingType.Color,
			field = "targetHighlightColor",
			parentId = "targetHighlight",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].highlightTarget and DEFAULTS[kind].highlightTarget.color) or GFH.COLOR_YELLOW,
			get = function()
				local hcfg, def = getHighlightCfg("highlightTarget")
				local r, g, b, a = unpackColor(hcfg.color, def.color or GFH.COLOR_YELLOW)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.highlightTarget = cfg.highlightTarget or {}
				cfg.highlightTarget.color = { value.r or 1, value.g or 1, value.b or 0, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "targetHighlightColor", cfg.highlightTarget.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightTarget") end,
		},
		{
			name = L["Texture"] or "Texture",
			kind = SettingType.Dropdown,
			field = "targetHighlightTexture",
			parentId = "targetHighlight",
			height = 180,
			get = function()
				local hcfg, def = getHighlightCfg("highlightTarget")
				return hcfg.texture or def.texture or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightTarget = cfg.highlightTarget or {}
				cfg.highlightTarget.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "targetHighlightTexture", cfg.highlightTarget.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(borderOptions()) do
					root:CreateRadio(option.label, function()
						local hcfg, def = getHighlightCfg("highlightTarget")
						return (hcfg.texture or def.texture or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.highlightTarget = cfg.highlightTarget or {}
						cfg.highlightTarget.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "targetHighlightTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isHighlightEnabled("highlightTarget") end,
		},
		{
			name = L["UFTargetHighlightLayer"] or "Layer",
			kind = SettingType.Dropdown,
			field = "targetHighlightLayer",
			parentId = "targetHighlight",
			default = (DEFAULTS[kind] and DEFAULTS[kind].highlightTarget and DEFAULTS[kind].highlightTarget.layer) or "ABOVE_BORDER",
			customDefaultText = getTargetHighlightLayerLabel(),
			get = function() return getTargetHighlightLayerValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightTarget = cfg.highlightTarget or {}
				local layer = tostring(value or "ABOVE_BORDER"):upper()
				if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
				cfg.highlightTarget.layer = layer
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "targetHighlightLayer", cfg.highlightTarget.layer, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = targetHighlightLayerGenerator(),
			isEnabled = function() return isHighlightEnabled("highlightTarget") end,
		},
		{
			name = L["Size"] or "Size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "targetHighlightSize",
			parentId = "targetHighlight",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local hcfg, def = getHighlightCfg("highlightTarget")
				return hcfg.size or def.size or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightTarget = cfg.highlightTarget or {}
				cfg.highlightTarget.size = clampNumber(value, 1, 64, cfg.highlightTarget.size or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "targetHighlightSize", cfg.highlightTarget.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightTarget") end,
		},
		{
			name = L["Offset"] or "Offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "targetHighlightOffset",
			parentId = "targetHighlight",
			minValue = -64,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local hcfg, def = getHighlightCfg("highlightTarget")
				return hcfg.offset or def.offset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.highlightTarget = cfg.highlightTarget or {}
				cfg.highlightTarget.offset = clampNumber(value, -64, 64, cfg.highlightTarget.offset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "targetHighlightOffset", cfg.highlightTarget.offset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHighlightEnabled("highlightTarget") end,
		},
		{
			name = L["Portrait"] or "Portrait",
			kind = SettingType.Collapsible,
			id = "portrait",
			defaultCollapsed = true,
			isShown = function() return kind ~= "raid" end,
		},
		{
			name = L["Enable portrait"] or "Enable portrait",
			kind = SettingType.Checkbox,
			field = "portraitEnabled",
			parentId = "portrait",
			isShown = function() return kind ~= "raid" end,
			get = function() return isPortraitEnabled() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitEnabled", cfg.portrait.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["UFPortraitMode"] or "Portrait mode",
			kind = SettingType.Dropdown,
			field = "portraitMode",
			parentId = "portrait",
			values = portraitModeOptions,
			isShown = function() return kind ~= "raid" end,
			get = function() return getPortraitModeValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.mode = tostring(value or "PORTRAIT"):upper()
				if cfg.portrait.mode ~= "CLASS_ICON" then cfg.portrait.mode = "PORTRAIT" end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitMode", cfg.portrait.mode, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() end,
		},
		{
			name = L["Portrait side"] or "Portrait side",
			kind = SettingType.Dropdown,
			field = "portraitSide",
			parentId = "portrait",
			values = portraitSideOptions,
			isShown = function() return kind ~= "raid" end,
			get = function() return getPortraitSideValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.side = tostring(value or "LEFT"):upper()
				if cfg.portrait.side ~= "RIGHT" then cfg.portrait.side = "LEFT" end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitSide", cfg.portrait.side, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() end,
		},
		{
			name = L["Force square background"] or "Force square background",
			kind = SettingType.Checkbox,
			field = "portraitSquareBackground",
			parentId = "portrait",
			isShown = function() return kind ~= "raid" end,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local value = pcfg.squareBackground
				if value == nil then value = defPortrait.squareBackground end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.squareBackground = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitSquareBackground", cfg.portrait.squareBackground, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() end,
		},
		{
			name = L["Extend border over portrait"] or "Extend border over portrait",
			kind = SettingType.Checkbox,
			field = "portraitBorderWithFrame",
			parentId = "portrait",
			isShown = function() return kind ~= "raid" end,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local value = pcfg.borderWithFrame
				if value == nil then value = defPortrait.borderWithFrame end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.borderWithFrame = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitBorderWithFrame", cfg.portrait.borderWithFrame, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				if not isPortraitEnabled() then return false end
				local cfg = getCfg(kind)
				local bc = cfg and cfg.border or {}
				local bdef = (DEFAULTS[kind] and DEFAULTS[kind].border) or {}
				local enabled = bc.enabled
				if enabled == nil then enabled = bdef.enabled end
				return enabled == true
			end,
		},
		{
			name = "Detach portrait",
			kind = SettingType.Checkbox,
			field = "portraitDetached",
			parentId = "portrait",
			isShown = function() return kind == "party" end,
			get = function()
				local cfg = getCfg(kind)
				return select(1, GF.ResolveGroupPortraitDetachedConfig(cfg, kind))
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.detached = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitDetached", cfg.portrait.detached, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			isEnabled = function() return isPortraitEnabled() end,
		},
		{
			name = "Portrait size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "portraitDetachedSize",
			parentId = "portrait",
			isShown = function() return kind == "party" and isPortraitDetached() end,
			minValue = 8,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				return pcfg.detachedSize or cfg.height or (DEFAULTS[kind] and DEFAULTS[kind].height) or 24
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.detachedSize = clampNumber(value, 8, 200, cfg.portrait.detachedSize or cfg.height or 24)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitDetachedSize", cfg.portrait.detachedSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() and isPortraitDetached() end,
		},
		{
			name = "Portrait offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "portraitDetachedOffsetX",
			parentId = "portrait",
			isShown = function() return kind == "party" and isPortraitDetached() end,
			minValue = -1000,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				if pcfg.detachedOffset and pcfg.detachedOffset.x ~= nil then return pcfg.detachedOffset.x end
				local size = pcfg.detachedSize or cfg.height or (DEFAULTS[kind] and DEFAULTS[kind].height) or 24
				local x = GF.GetDefaultDetachedPortraitOffset(cfg, kind, cfg.width, cfg.height, size)
				return x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.detachedOffset = cfg.portrait.detachedOffset or {}
				cfg.portrait.detachedOffset.x = clampNumber(value, -1000, 1000, cfg.portrait.detachedOffset.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitDetachedOffsetX", cfg.portrait.detachedOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() and isPortraitDetached() end,
		},
		{
			name = "Portrait offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "portraitDetachedOffsetY",
			parentId = "portrait",
			isShown = function() return kind == "party" and isPortraitDetached() end,
			minValue = -1000,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				if pcfg.detachedOffset and pcfg.detachedOffset.y ~= nil then return pcfg.detachedOffset.y end
				local size = pcfg.detachedSize or cfg.height or (DEFAULTS[kind] and DEFAULTS[kind].height) or 24
				local _, y = GF.GetDefaultDetachedPortraitOffset(cfg, kind, cfg.width, cfg.height, size)
				return y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.detachedOffset = cfg.portrait.detachedOffset or {}
				cfg.portrait.detachedOffset.y = clampNumber(value, -1000, 1000, cfg.portrait.detachedOffset.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitDetachedOffsetY", cfg.portrait.detachedOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() and isPortraitDetached() end,
		},
		{
			name = "Portrait detached strata",
			kind = SettingType.Dropdown,
			field = "portraitDetachedStrata",
			parentId = "portrait",
			isShown = function() return kind == "party" and isPortraitDetached() end,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				return GF.NormalizeFrameStrataToken(pcfg.detachedStrata) or ""
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.detachedStrata = GF.NormalizeFrameStrataToken(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitDetachedStrata", cfg.portrait.detachedStrata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			getValueText = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				return GF.DropdownOptionLabel(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT, GF.NormalizeFrameStrataToken(pcfg.detachedStrata) or "", DEFAULT or "Default")
			end,
			generator = GF.DropdownRadioGenerator(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT),
			isEnabled = function() return isPortraitEnabled() and isPortraitDetached() end,
		},
		{
			name = "Portrait detached level",
			kind = SettingType.Slider,
			allowInput = true,
			field = "portraitDetachedFrameLevelOffset",
			parentId = "portrait",
			isShown = function() return kind == "party" and isPortraitDetached() end,
			minValue = -20,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local value = pcfg.detachedFrameLevelOffset
				if value == nil then value = 1 end
				value = clampNumber(value, -20, 1000, 1)
				return floor(value + (value >= 0 and 0.5 or -0.5))
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				local levelOffset = clampNumber(value, -20, 1000, cfg.portrait.detachedFrameLevelOffset or 1)
				levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
				cfg.portrait.detachedFrameLevelOffset = levelOffset
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitDetachedFrameLevelOffset", cfg.portrait.detachedFrameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() and isPortraitDetached() end,
		},
		{
			name = L["Show separator"] or "Show separator",
			kind = SettingType.Checkbox,
			field = "portraitSeparatorEnabled",
			parentId = "portrait",
			isShown = function() return kind ~= "raid" end,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local scfg = pcfg.separator or {}
				local sdef = (defPortrait and defPortrait.separator) or {}
				local value = scfg.enabled
				if value == nil then value = sdef.enabled end
				if value == nil then value = true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.separator = cfg.portrait.separator or {}
				cfg.portrait.separator.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitSeparatorEnabled", cfg.portrait.separator.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPortraitEnabled() and not isPortraitDetached() end,
		},
		{
			name = L["Separator size"] or "Separator size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "portraitSeparatorSize",
			parentId = "portrait",
			isShown = function() return kind ~= "raid" end,
			minValue = 1,
			maxValue = 32,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local scfg = pcfg.separator or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local sdef = (defPortrait and defPortrait.separator) or {}
				local size = tonumber(scfg.size)
				if not size then size = tonumber(sdef.size) end
				if not size then
					local bc = cfg and cfg.border or {}
					local bdef = (DEFAULTS[kind] and DEFAULTS[kind].border) or {}
					size = tonumber(bc.edgeSize or bdef.edgeSize) or 1
				end
				if size < 1 then size = 1 end
				return size
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.separator = cfg.portrait.separator or {}
				cfg.portrait.separator.size = clampNumber(value, 1, 32, cfg.portrait.separator.size or 1)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitSeparatorSize", cfg.portrait.separator.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				if not isPortraitEnabled() then return false end
				if isPortraitDetached() then return false end
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local scfg = pcfg.separator or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local sdef = (defPortrait and defPortrait.separator) or {}
				local value = scfg.enabled
				if value == nil then value = sdef.enabled end
				if value == nil then value = true end
				return value == true
			end,
		},
		{
			name = L["Custom separator color"] or "Custom separator color",
			kind = SettingType.Checkbox,
			field = "portraitSeparatorUseCustomColor",
			parentId = "portrait",
			isShown = function() return kind ~= "raid" end,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local scfg = pcfg.separator or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local sdef = (defPortrait and defPortrait.separator) or {}
				local value = scfg.useCustomColor
				if value == nil then value = sdef.useCustomColor end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.separator = cfg.portrait.separator or {}
				cfg.portrait.separator.useCustomColor = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitSeparatorUseCustomColor", cfg.portrait.separator.useCustomColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				if not isPortraitEnabled() then return false end
				if isPortraitDetached() then return false end
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local scfg = pcfg.separator or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local sdef = (defPortrait and defPortrait.separator) or {}
				local value = scfg.enabled
				if value == nil then value = sdef.enabled end
				if value == nil then value = true end
				return value == true
			end,
		},
		{
			name = L["Separator color"] or "Separator color",
			kind = SettingType.Color,
			field = "portraitSeparatorColor",
			parentId = "portrait",
			isShown = function() return kind ~= "raid" end,
			hasOpacity = true,
			default = (
				(DEFAULTS[kind] and DEFAULTS[kind].portrait and DEFAULTS[kind].portrait.separator and DEFAULTS[kind].portrait.separator.color)
				or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.color)
				or { 0, 0, 0, 0.8 }
			),
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local scfg = pcfg.separator or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local sdef = (defPortrait and defPortrait.separator) or {}
				local bc = cfg and cfg.border or {}
				local bdef = (DEFAULTS[kind] and DEFAULTS[kind].border) or {}
				local fallback = sdef.color or bc.color or bdef.color or { 0, 0, 0, 0.8 }
				local r, g, b, a = unpackColor(scfg.color, fallback)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.portrait = cfg.portrait or {}
				cfg.portrait.separator = cfg.portrait.separator or {}
				cfg.portrait.separator.color = { value.r or 0, value.g or 0, value.b or 0, value.a or 0.8 }
				cfg.portrait.separator.useCustomColor = true
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitSeparatorColor", cfg.portrait.separator.color, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "portraitSeparatorUseCustomColor", true, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				if not isPortraitEnabled() then return false end
				if isPortraitDetached() then return false end
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.portrait or {}
				local scfg = pcfg.separator or {}
				local defPortrait = (DEFAULTS[kind] and DEFAULTS[kind].portrait) or {}
				local sdef = (defPortrait and defPortrait.separator) or {}
				local separatorEnabled = scfg.enabled
				if separatorEnabled == nil then separatorEnabled = sdef.enabled end
				if separatorEnabled == nil then separatorEnabled = true end
				if not separatorEnabled then return false end
				local useCustomColor = scfg.useCustomColor
				if useCustomColor == nil then useCustomColor = sdef.useCustomColor end
				return useCustomColor == true
			end,
		},
		{
			name = L["Name"] or "Name",
			kind = SettingType.Collapsible,
			id = "text",
			defaultCollapsed = true,
		},
		{
			name = L["Show name"] or "Show name",
			kind = SettingType.Checkbox,
			field = "showName",
			parentId = "text",
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.showName = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "showName", cfg.text.showName, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshNames()
			end,
		},
		{
			name = L["Name anchor"] or "Name anchor",
			kind = SettingType.Dropdown,
			field = "nameAnchor",
			parentId = "text",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.nameAnchor or (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameAnchor) or "LEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.nameAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["Name offset X"] or "Name offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "nameOffsetX",
			parentId = "text",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return (tc.nameOffset and tc.nameOffset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.nameOffset = cfg.text.nameOffset or {}
				cfg.text.nameOffset.x = clampNumber(value, -200, 200, (cfg.text.nameOffset and cfg.text.nameOffset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameOffsetX", cfg.text.nameOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["Name offset Y"] or "Name offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "nameOffsetY",
			parentId = "text",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return (tc.nameOffset and tc.nameOffset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.nameOffset = cfg.text.nameOffset or {}
				cfg.text.nameOffset.y = clampNumber(value, -200, 200, (cfg.text.nameOffset and cfg.text.nameOffset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameOffsetY", cfg.text.nameOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["Name class color"] or "Name class color",
			kind = SettingType.Checkbox,
			field = "nameClassColor",
			parentId = "text",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				if sc.nameColorMode then return sc.nameColorMode == "CLASS" end
				local tc = cfg and cfg.text or {}
				return tc.useClassColor ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.status = cfg.status or {}
				cfg.text.useClassColor = value and true or false
				cfg.status.nameColorMode = value and "CLASS" or "CUSTOM"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameClassColor", cfg.text.useClassColor, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameColorMode", cfg.status.nameColorMode, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["Name color"] or "Name color",
			kind = SettingType.Color,
			field = "nameColor",
			parentId = "text",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.nameColor) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.nameColor) or { 1, 1, 1, 1 }
				local r, g, b, a = unpackColor(sc.nameColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.status = cfg.status or {}
				cfg.text = cfg.text or {}
				cfg.status.nameColor = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				cfg.status.nameColorMode = "CUSTOM"
				cfg.text.useClassColor = false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameColor", cfg.status.nameColor, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameClassColor", false, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameColorMode", "CUSTOM", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local mode = sc.nameColorMode
				if not mode then
					local tc = cfg and cfg.text or {}
					mode = (tc.useClassColor ~= false) and "CLASS" or "CUSTOM"
				end
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false and mode == "CUSTOM"
			end,
		},
		{
			name = L["Name max width"] or "Name max width",
			kind = SettingType.Slider,
			allowInput = true,
			field = "nameMaxChars",
			parentId = "text",
			minValue = 0,
			maxValue = 40,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameMaxChars) or 0,
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.nameMaxChars or (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameMaxChars) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.nameMaxChars = clampNumber(value, 0, 40, cfg.text.nameMaxChars or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameMaxChars", cfg.text.nameMaxChars, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshNames()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["Hide ellipsis"] or "Hide ellipsis",
			kind = SettingType.Checkbox,
			field = "nameNoEllipsis",
			parentId = "text",
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameNoEllipsis) or false
				if tc.nameNoEllipsis == nil then return def == true end
				return tc.nameNoEllipsis == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.nameNoEllipsis = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameNoEllipsis", cfg.text.nameNoEllipsis, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshNames()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				local maxChars = tonumber(tc.nameMaxChars) or 0
				return tc.showName ~= false and maxChars > 0
			end,
		},
		{
			name = L["Name font size"] or "Name font size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "nameFontSize",
			parentId = "text",
			minValue = 8,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.fontSize or (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.fontSize) or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.fontSize = clampNumber(value, 8, 30, cfg.text.fontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameFontSize", cfg.text.fontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["Name font"] or "Name font",
			kind = SettingType.Dropdown,
			field = "nameFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "text",
			default = GF.GlobalFontConfigKey(),
			customDefaultText = (function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return GF.DropdownOptionLabel(GF.FontOptionsWithDefault, tc.font or GF.GlobalFontConfigKey(), DEFAULT or "Default")
			end)(),
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.font or GF.GlobalFontConfigKey()
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.font = (value ~= nil and value ~= "") and value or GF.GlobalFontConfigKey()
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameFont", cfg.text.font, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = GF.DropdownRadioGenerator(GF.FontOptionsWithDefault),
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["Name font outline"] or "Name font outline",
			kind = SettingType.Dropdown,
			field = "nameFontOutline",
			parentId = "text",
			get = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return normalizeFontStyleChoice(tc.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.fontOutline) or "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.text = cfg.text or {}
				cfg.text.fontOutline = normalizeFontStyleChoice(value, (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.fontOutline) or "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameFontOutline", cfg.text.fontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
				generator = function(_, root)
					for _, option in ipairs(GF._sharedEdit.outlineOpts) do
						root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local tc = cfg and cfg.text or {}
						return normalizeFontStyleChoice(tc.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.fontOutline) or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.text = cfg.text or {}
						cfg.text.fontOutline = normalizeFontStyleChoice(option.value, (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.fontOutline) or "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameFontOutline", cfg.text.fontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = string.format("%s (%s)", L["Frame strata"] or "Frame strata", NAME or "Name"),
			kind = SettingType.Dropdown,
			field = "nameStrata",
			parentId = "text",
			default = "",
			customDefaultText = (function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return GF.DropdownOptionLabel(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT, GF.NormalizeFrameStrataToken(sc.nameStrata) or "", DEFAULT or "Default")
			end)(),
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return GF.NormalizeFrameStrataToken(sc.nameStrata) or ""
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.nameStrata = GF.NormalizeFrameStrataToken(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameStrata", cfg.status.nameStrata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = GF.DropdownRadioGenerator(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT),
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = string.format("%s (%s)", L["UFFrameLevel"] or "Frame level", NAME or "Name"),
			kind = SettingType.Slider,
			allowInput = true,
			field = "nameFrameLevelOffset",
			parentId = "text",
			minValue = -20,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.nameFrameLevelOffset or 5
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.nameFrameLevelOffset = clampNumber(value, -20, 50, cfg.status.nameFrameLevelOffset or 5)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "nameFrameLevelOffset", cfg.status.nameFrameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local tc = cfg and cfg.text or {}
				return tc.showName ~= false
			end,
		},
		{
			name = L["UFDataBar"] or "Data bar",
			kind = SettingType.Collapsible,
			id = "dataBar",
			defaultCollapsed = true,
		},
		{
			name = L["UFDataBarEnable"] or "Enable data bar",
			kind = SettingType.Checkbox,
			field = "dataBarEnabled",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or copyDefaultsTable((DEFAULTS[kind] and DEFAULTS[kind].dataBar) or GF.CreateDataBarDefaults())
				cfg.dataBar.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarEnabled", cfg.dataBar.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["UFDataBarPosition"] or "Data bar position",
			kind = SettingType.Dropdown,
			field = "dataBarPosition",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.position or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.position) or "BELOW"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.position = (value == "ABOVE") and "ABOVE" or "BELOW"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarPosition", cfg.dataBar.position, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				local options = {
					{ value = "ABOVE", label = L["UFDataBarAbove"] or "Above" },
					{ value = "BELOW", label = L["UFDataBarBelow"] or "Below" },
				}
				for _, option in ipairs(options) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local dbc = cfg and cfg.dataBar or {}
						return (dbc.position or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.position) or "BELOW") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.dataBar = cfg.dataBar or {}
						cfg.dataBar.position = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarPosition", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["UFDataBarHeight"] or "Data bar height",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarHeight",
			parentId = "dataBar",
			minValue = 1,
			maxValue = 40,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.height or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.height) or 16
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.height = clampNumber(value, 1, 40, cfg.dataBar.height or 16)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarHeight", cfg.dataBar.height, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["UFDataBarGap"] or "Data bar gap",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarGap",
			parentId = "dataBar",
			minValue = -20,
			maxValue = 40,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.gap or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.gap) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.gap = clampNumber(value, -20, 40, cfg.dataBar.gap or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarGap", cfg.dataBar.gap, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["UFDataBarDetached"] or "Detach data bar",
			kind = SettingType.Checkbox,
			field = "dataBarDetached",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.detached == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.detached = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetached", cfg.dataBar.detached, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			isEnabled = isDataBarEnabled,
			isShown = isDataBarEnabled,
		},
		{
			name = L["UFDataBarDetachedMatchHealthWidth"] or "Match health width",
			kind = SettingType.Checkbox,
			field = "dataBarDetachedMatchHealthWidth",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].dataBar or {}
				local value = dbc.detachedMatchHealthWidth
				if value == nil then value = def.detachedMatchHealthWidth end
				if value == nil then value = true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.detachedMatchHealthWidth = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetachedMatchHealthWidth", cfg.dataBar.detachedMatchHealthWidth, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDataBarDetached,
			isShown = isDataBarDetached,
		},
		{
			name = L["UFDataBarWidth"] or "Data bar width",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarDetachedWidth",
			parentId = "dataBar",
			minValue = 10,
			maxValue = 600,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.detachedWidth or cfg.width or (DEFAULTS[kind] and DEFAULTS[kind].width) or 100
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.detachedWidth = clampNumber(value, 10, 600, cfg.dataBar.detachedWidth or cfg.width or 100)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetachedWidth", cfg.dataBar.detachedWidth, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDetachedDataBarWidthUnlocked,
			isShown = isDataBarDetached,
		},
		{
			name = L["Grow from center"] or "Grow from center",
			kind = SettingType.Checkbox,
			field = "dataBarDetachedGrowFromCenter",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].dataBar or {}
				local value = dbc.detachedGrowFromCenter
				if value == nil then value = def.detachedGrowFromCenter end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.detachedGrowFromCenter = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetachedGrowFromCenter", cfg.dataBar.detachedGrowFromCenter, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDataBarDetached,
			isShown = isDataBarDetached,
		},
		{
			name = L["Offset X"] or "Offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarDetachedOffsetX",
			parentId = "dataBar",
			minValue = -400,
			maxValue = 400,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return (dbc.detachedOffset and dbc.detachedOffset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.detachedOffset = cfg.dataBar.detachedOffset or {}
				cfg.dataBar.detachedOffset.x = clampNumber(value, -400, 400, cfg.dataBar.detachedOffset.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetachedOffsetX", cfg.dataBar.detachedOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDataBarDetached,
			isShown = isDataBarDetached,
		},
		{
			name = L["Offset Y"] or "Offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarDetachedOffsetY",
			parentId = "dataBar",
			minValue = -400,
			maxValue = 400,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return (dbc.detachedOffset and dbc.detachedOffset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.detachedOffset = cfg.dataBar.detachedOffset or {}
				cfg.dataBar.detachedOffset.y = clampNumber(value, -400, 400, cfg.dataBar.detachedOffset.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetachedOffsetY", cfg.dataBar.detachedOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDataBarDetached,
			isShown = isDataBarDetached,
		},
		{
			name = L["UFDetachedPowerStrata"] or "Strata",
			kind = SettingType.Dropdown,
			field = "dataBarDetachedStrata",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return GF.NormalizeFrameStrataToken(dbc.detachedStrata) or ""
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.detachedStrata = GF.NormalizeFrameStrataToken(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetachedStrata", cfg.dataBar.detachedStrata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			getValueText = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return GF.DropdownOptionLabel(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT, GF.NormalizeFrameStrataToken(dbc.detachedStrata) or "", DEFAULT or "Default")
			end,
			generator = GF.DropdownRadioGenerator(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT),
			isEnabled = isDataBarDetached,
			isShown = isDataBarDetached,
		},
		{
			name = L["UFDetachedPowerLevelOffset"] or "Frame level offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarDetachedFrameLevelOffset",
			parentId = "dataBar",
			minValue = -20,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				local value = dbc.detachedFrameLevelOffset
				if value == nil then value = (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.detachedFrameLevelOffset) end
				value = clampNumber(value, -20, 1000, 2)
				return floor(value + (value >= 0 and 0.5 or -0.5))
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				local levelOffset = clampNumber(value, -20, 1000, cfg.dataBar.detachedFrameLevelOffset or 2)
				levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
				cfg.dataBar.detachedFrameLevelOffset = levelOffset
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarDetachedFrameLevelOffset", cfg.dataBar.detachedFrameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDataBarDetached,
			isShown = isDataBarDetached,
		},
		{
			name = L["Use class color (players)"] or "Use class color (players)",
			kind = SettingType.Checkbox,
			field = "dataBarClassColor",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.useClassColor == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.useClassColor = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarClassColor", cfg.dataBar.useClassColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["UFDataBarColor"] or "Data bar color",
			kind = SettingType.Color,
			field = "dataBarColor",
			parentId = "dataBar",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.color) or { 0.18, 0.18, 0.22, 1 },
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.color) or { 0.18, 0.18, 0.22, 1 }
				local r, g, b, a = unpackColor(dbc.color, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.color = { value.r or 0.18, value.g or 0.18, value.b or 0.22, value.a or 1 }
				cfg.dataBar.useClassColor = false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarColor", cfg.dataBar.color, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarClassColor", false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.enabled == true and dbc.useClassColor ~= true
			end,
		},
		{
			name = L["Bar texture"] or "Bar texture",
			kind = SettingType.Dropdown,
			field = "dataBarTexture",
			parentId = "dataBar",
			height = 200,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.texture or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.texture) or "SOLID"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.texture = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTexture", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local dbc = cfg and cfg.dataBar or {}
						return (dbc.texture or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.texture) or "SOLID") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.dataBar = cfg.dataBar or {}
						cfg.dataBar.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Left text"] or "Left text",
			kind = SettingType.Dropdown,
			field = "dataBarTextLeft",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.textLeft or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textLeft) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.textLeft = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextLeft", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.dataBarTextModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local dbc = cfg and cfg.dataBar or {}
						return (dbc.textLeft or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textLeft) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.dataBar = cfg.dataBar or {}
						cfg.dataBar.textLeft = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextLeft", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						requestEditModeSettingsRefresh()
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Center text"] or "Center text",
			kind = SettingType.Dropdown,
			field = "dataBarTextCenter",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.textCenter or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textCenter) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.textCenter = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextCenter", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.dataBarTextModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local dbc = cfg and cfg.dataBar or {}
						return (dbc.textCenter or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textCenter) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.dataBar = cfg.dataBar or {}
						cfg.dataBar.textCenter = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextCenter", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						requestEditModeSettingsRefresh()
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Right text"] or "Right text",
			kind = SettingType.Dropdown,
			field = "dataBarTextRight",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.textRight or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textRight) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.textRight = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextRight", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.dataBarTextModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local dbc = cfg and cfg.dataBar or {}
						return (dbc.textRight or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textRight) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.dataBar = cfg.dataBar or {}
						cfg.dataBar.textRight = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextRight", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						requestEditModeSettingsRefresh()
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Name max width"] or "Name max width",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarNameMaxChars",
			parentId = "dataBar",
			minValue = 0,
			maxValue = 40,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.nameMaxChars) or 0,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.nameMaxChars or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.nameMaxChars) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.nameMaxChars = clampNumber(value, 0, 40, cfg.dataBar.nameMaxChars or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarNameMaxChars", cfg.dataBar.nameMaxChars, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDataBarNameTextEnabled,
			isShown = isDataBarNameTextEnabled,
		},
		{
			name = L["UFDataBarTextUseClassColor"] or "Use class color (data bar text)",
			kind = SettingType.Checkbox,
			field = "dataBarTextClassColor",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.useTextClassColor == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.useTextClassColor = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextClassColor", cfg.dataBar.useTextClassColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["UFDataBarTextColor"] or "Data bar text color",
			kind = SettingType.Color,
			field = "dataBarTextColor",
			parentId = "dataBar",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textColor) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.textColor) or { 1, 1, 1, 1 }
				local r, g, b, a = unpackColor(dbc.textColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.textColor = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				cfg.dataBar.useTextClassColor = false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextColor", cfg.dataBar.textColor, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarTextClassColor", false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return cfg and dbc.enabled == true and dbc.useTextClassColor ~= true
			end,
		},
		{
			name = FONT_SIZE,
			kind = SettingType.Slider,
			allowInput = true,
			field = "dataBarFontSize",
			parentId = "dataBar",
			minValue = 8,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.fontSize or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.fontSize) or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.fontSize = clampNumber(value, 8, 30, cfg.dataBar.fontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarFontSize", cfg.dataBar.fontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Font"] or "Font",
			kind = SettingType.Dropdown,
			field = "dataBarFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.font or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.font) or GF.GlobalFontConfigKey()
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.font = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local dbc = cfg and cfg.dataBar or {}
						return (dbc.font or (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.font) or GF.GlobalFontConfigKey()) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.dataBar = cfg.dataBar or {}
						cfg.dataBar.font = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Font outline"] or "Font outline",
			kind = SettingType.Dropdown,
			field = "dataBarFontOutline",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return normalizeFontStyleChoice(dbc.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.fontOutline) or "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.fontOutline = normalizeFontStyleChoice(value, (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.fontOutline) or "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarFontOutline", cfg.dataBar.fontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local dbc = cfg and cfg.dataBar or {}
						return normalizeFontStyleChoice(dbc.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.fontOutline) or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.dataBar = cfg.dataBar or {}
						cfg.dataBar.fontOutline = normalizeFontStyleChoice(option.value, (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.fontOutline) or "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarFontOutline", cfg.dataBar.fontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Use short numbers"] or "Use short numbers",
			kind = SettingType.Checkbox,
			field = "dataBarShortNumbers",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				if dbc.useShortNumbers == nil then return (DEFAULTS[kind] and DEFAULTS[kind].dataBar and DEFAULTS[kind].dataBar.useShortNumbers) ~= false end
				return dbc.useShortNumbers ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.useShortNumbers = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarShortNumbers", cfg.dataBar.useShortNumbers, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Round percent values"] or "Round percent values",
			kind = SettingType.Checkbox,
			field = "dataBarRoundPercent",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.roundPercent == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.roundPercent = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarRoundPercent", cfg.dataBar.roundPercent, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Hide % symbol"] or "Hide % symbol",
			kind = SettingType.Checkbox,
			field = "dataBarHidePercent",
			parentId = "dataBar",
			get = function()
				local cfg = getCfg(kind)
				local dbc = cfg and cfg.dataBar or {}
				return dbc.hidePercentSymbol == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dataBar = cfg.dataBar or {}
				cfg.dataBar.hidePercentSymbol = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dataBarHidePercent", cfg.dataBar.hidePercentSymbol, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return cfg and cfg.dataBar and cfg.dataBar.enabled == true
			end,
		},
		{
			name = L["Health"] or "Health",
			kind = SettingType.Collapsible,
			id = "health",
			defaultCollapsed = true,
		},
		{
			name = L["Use class color (players)"] or "Use class color (players)",
			kind = SettingType.Checkbox,
			field = "healthClassColor",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.useClassColor == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.useClassColor = value and true or false
				if value then cfg.health.useCustomColor = false end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthClassColor", cfg.health.useClassColor, nil, true) end
				if EditMode and EditMode.SetValue and value then EditMode:SetValue(editModeId, "healthUseCustomColor", false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:UpdateHealthColorMode(kind)
			end,
		},
		{
			name = L["Custom health color"] or "Custom health color",
			kind = SettingType.Checkbox,
			field = "healthUseCustomColor",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.useCustomColor == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.useCustomColor = value and true or false
				if value then cfg.health.useClassColor = false end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthUseCustomColor", cfg.health.useCustomColor, nil, true) end
				if EditMode and EditMode.SetValue and value then EditMode:SetValue(editModeId, "healthClassColor", false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:UpdateHealthColorMode(kind)
			end,
		},
		{
			name = L["Health color"] or "Health color",
			kind = SettingType.Color,
			field = "healthColor",
			parentId = "health",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.color) or { 0, 0.8, 0, 1 },
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.color) or { 0, 0.8, 0, 1 }
				local r, g, b, a = unpackColor(hc.color, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.health = cfg.health or {}
				cfg.health.color = { value.r or 0, value.g or 0.8, value.b or 0, value.a or 1 }
				cfg.health.useCustomColor = true
				cfg.health.useClassColor = false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthColor", cfg.health.color, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthUseCustomColor", true, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthClassColor", false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.useCustomColor == true
			end,
		},
		{
			name = L["Left text"] or "Left text",
			kind = SettingType.Dropdown,
			field = "healthTextLeft",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.textLeft or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textLeft) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.textLeft = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextLeft", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.healthTextModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.textLeft or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textLeft) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.textLeft = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextLeft", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						end)
					end
				end,
				},
		{
			name = L["Center text"] or "Center text",
			kind = SettingType.Dropdown,
			field = "healthTextCenter",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.textCenter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textCenter) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.textCenter = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextCenter", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.healthTextModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.textCenter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textCenter) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.textCenter = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextCenter", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Right text"] or "Right text",
			kind = SettingType.Dropdown,
			field = "healthTextRight",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.textRight or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textRight) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.textRight = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextRight", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.healthTextModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.textRight or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textRight) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.textRight = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextRight", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Health text color"] or "Health text color",
			kind = SettingType.Color,
			field = "healthTextColor",
			parentId = "health",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textColor) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textColor) or { 1, 1, 1, 1 }
				local r, g, b, a = unpackColor(hc.textColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.health = cfg.health or {}
				cfg.health.textColor = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				cfg.health.useTextClassColor = false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextColor", cfg.health.textColor, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextClassColor", false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				if not anyHealthTextEnabled() then return false end
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.useTextClassColor ~= true
			end,
		},
		{
			name = L["Use class color (health text)"] or "Use class color (health text)",
			kind = SettingType.Checkbox,
			field = "healthTextClassColor",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.useTextClassColor == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.useTextClassColor = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTextClassColor", cfg.health.useTextClassColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return anyHealthTextEnabled() end,
		},
		{
			name = L["Hide % symbol"] or "Hide % symbol",
			kind = SettingType.Checkbox,
			field = "healthHidePercent",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.hidePercentSymbol == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.hidePercentSymbol = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthHidePercent", cfg.health.hidePercentSymbol, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = FONT_SIZE,
			kind = SettingType.Slider,
			allowInput = true,
			field = "healthFontSize",
			parentId = "health",
			minValue = 8,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.fontSize or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.fontSize) or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.fontSize = clampNumber(value, 8, 30, cfg.health.fontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthFontSize", cfg.health.fontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Font"] or "Font",
			kind = SettingType.Dropdown,
			field = "healthFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.font or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.font) or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.font = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthFont", cfg.health.font, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.font or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.font) or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.font = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Font outline"] or "Font outline",
			kind = SettingType.Dropdown,
			field = "healthFontOutline",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return normalizeFontStyleChoice(hc.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.fontOutline) or "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.fontOutline = normalizeFontStyleChoice(value, (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.fontOutline) or "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthFontOutline", cfg.health.fontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return normalizeFontStyleChoice(hc.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.fontOutline) or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.fontOutline = normalizeFontStyleChoice(option.value, (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.fontOutline) or "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthFontOutline", cfg.health.fontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Use short numbers"] or "Use short numbers",
			kind = SettingType.Checkbox,
			field = "healthShortNumbers",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				if hc.useShortNumbers == nil then return (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.useShortNumbers) ~= false end
				return hc.useShortNumbers ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.useShortNumbers = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthShortNumbers", cfg.health.useShortNumbers, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Round percent values"] or "Round percent values",
			kind = SettingType.Checkbox,
			field = "healthRoundPercent",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.roundPercent == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.roundPercent = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthRoundPercent", cfg.health.roundPercent, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Delimiter"] or "Delimiter",
			kind = SettingType.Dropdown,
			field = "healthDelimiter",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiter) or " "
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.textDelimiter = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthDelimiter", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.delimiterOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiter) or " ") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.textDelimiter = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthDelimiter", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isShown = function() return healthDelimiterCount() >= 1 end,
		},
		{
			name = L["Secondary Delimiter"] or "Secondary delimiter",
			kind = SettingType.Dropdown,
			field = "healthDelimiterSecondary",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local primary = hc.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiter) or " "
				return hc.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterSecondary) or primary
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.textDelimiterSecondary = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthDelimiterSecondary", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.delimiterOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						local primary = hc.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiter) or " "
						return (hc.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterSecondary) or primary) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.textDelimiterSecondary = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthDelimiterSecondary", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isShown = function() return healthDelimiterCount() >= 2 end,
		},
		{
			name = L["Tertiary Delimiter"] or "Tertiary delimiter",
			kind = SettingType.Dropdown,
			field = "healthDelimiterTertiary",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local primary = hc.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiter) or " "
				local secondary = hc.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterSecondary) or primary
				return hc.textDelimiterTertiary or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterTertiary) or secondary
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.textDelimiterTertiary = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthDelimiterTertiary", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.delimiterOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						local primary = hc.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiter) or " "
						local secondary = hc.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterSecondary) or primary
						return (hc.textDelimiterTertiary or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterTertiary) or secondary) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.textDelimiterTertiary = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthDelimiterTertiary", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isShown = function() return healthDelimiterCount() >= 3 end,
		},
		{
			name = L["TextLeftOffsetX"] or "Left text X offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "healthLeftX",
			parentId = "health",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return (hc.offsetLeft and hc.offsetLeft.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.offsetLeft = cfg.health.offsetLeft or {}
				cfg.health.offsetLeft.x = clampNumber(value, -200, 200, (cfg.health.offsetLeft and cfg.health.offsetLeft.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthLeftX", cfg.health.offsetLeft.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHealthTextEnabled("textLeft") end,
		},
		{
			name = L["TextLeftOffsetY"] or "Left text Y offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "healthLeftY",
			parentId = "health",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return (hc.offsetLeft and hc.offsetLeft.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.offsetLeft = cfg.health.offsetLeft or {}
				cfg.health.offsetLeft.y = clampNumber(value, -200, 200, (cfg.health.offsetLeft and cfg.health.offsetLeft.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthLeftY", cfg.health.offsetLeft.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHealthTextEnabled("textLeft") end,
		},
		{
			name = L["TextCenterOffsetX"] or "Center text X offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "healthCenterX",
			parentId = "health",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return (hc.offsetCenter and hc.offsetCenter.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.offsetCenter = cfg.health.offsetCenter or {}
				cfg.health.offsetCenter.x = clampNumber(value, -200, 200, (cfg.health.offsetCenter and cfg.health.offsetCenter.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthCenterX", cfg.health.offsetCenter.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHealthTextEnabled("textCenter") end,
		},
		{
			name = L["TextCenterOffsetY"] or "Center text Y offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "healthCenterY",
			parentId = "health",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return (hc.offsetCenter and hc.offsetCenter.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.offsetCenter = cfg.health.offsetCenter or {}
				cfg.health.offsetCenter.y = clampNumber(value, -200, 200, (cfg.health.offsetCenter and cfg.health.offsetCenter.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthCenterY", cfg.health.offsetCenter.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHealthTextEnabled("textCenter") end,
		},
		{
			name = L["TextRightOffsetX"] or "Right text X offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "healthRightX",
			parentId = "health",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return (hc.offsetRight and hc.offsetRight.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.offsetRight = cfg.health.offsetRight or {}
				cfg.health.offsetRight.x = clampNumber(value, -200, 200, (cfg.health.offsetRight and cfg.health.offsetRight.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthRightX", cfg.health.offsetRight.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHealthTextEnabled("textRight") end,
		},
		{
			name = L["TextRightOffsetY"] or "Right text Y offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "healthRightY",
			parentId = "health",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return (hc.offsetRight and hc.offsetRight.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.offsetRight = cfg.health.offsetRight or {}
				cfg.health.offsetRight.y = clampNumber(value, -200, 200, (cfg.health.offsetRight and cfg.health.offsetRight.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthRightY", cfg.health.offsetRight.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isHealthTextEnabled("textRight") end,
		},
		{
			name = L["Bar texture"] or "Bar texture",
			kind = SettingType.Dropdown,
			field = "healthTexture",
			parentId = "health",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.texture or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.texture) or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTexture", cfg.health.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.texture or (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.texture) or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return usesPerBarTextureSelection(cfg)
			end,
		},
		{
			name = L["Smooth fill"] or "Smooth fill",
			kind = SettingType.Checkbox,
			field = "healthSmoothFill",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].health or {}
				if hc.smoothFill == nil then return def.smoothFill == true end
				return hc.smoothFill == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.smoothFill = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthSmoothFill", cfg.health.smoothFill, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show temporary max health loss"] or "Show temporary max health loss",
			kind = SettingType.Checkbox,
			field = "healthTempMaxHealthLoss",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].health or {}
				if hc.tempMaxHealthLossEnabled == nil then return def.tempMaxHealthLossEnabled ~= false end
				return hc.tempMaxHealthLossEnabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.tempMaxHealthLossEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthTempMaxHealthLoss", cfg.health.tempMaxHealthLossEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show bar backdrop"] or "Show bar backdrop",
			kind = SettingType.Checkbox,
			field = "healthBackdropEnabled",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].health or {}
				local defBackdrop = def and def.backdrop or {}
				if hc.backdrop and hc.backdrop.enabled ~= nil then return hc.backdrop.enabled ~= false end
				return defBackdrop.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.backdrop = cfg.health.backdrop or {}
				cfg.health.backdrop.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthBackdropEnabled", cfg.health.backdrop.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Backdrop texture"] or "Backdrop texture",
			kind = SettingType.Dropdown,
			field = "healthBackdropTexture",
			parentId = "health",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].health or {}
				local defBackdrop = def and def.backdrop or {}
				return (hc.backdrop and hc.backdrop.texture) or defBackdrop.texture or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.backdrop = cfg.health.backdrop or {}
				cfg.health.backdrop.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthBackdropTexture", cfg.health.backdrop.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						local def = DEFAULTS[kind] and DEFAULTS[kind].health or {}
						local defBackdrop = def and def.backdrop or {}
						return ((hc.backdrop and hc.backdrop.texture) or defBackdrop.texture or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.backdrop = cfg.health.backdrop or {}
						cfg.health.backdrop.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthBackdropTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].health or {}
				local defBackdrop = def and def.backdrop or {}
				local enabled = hc.backdrop and hc.backdrop.enabled
				if enabled == nil then enabled = defBackdrop.enabled end
				if enabled == nil then enabled = true end
				return enabled ~= false
			end,
		},
		{
			name = L["Clamp backdrop to missing health"] or "Clamp backdrop to missing health",
			kind = SettingType.Checkbox,
			field = "healthBackdropClampToFill",
			parentId = "health",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].health or {}
				local defBackdrop = def and def.backdrop or {}
				local value = hc.backdrop and hc.backdrop.clampToFill
				if value == nil then value = defBackdrop.clampToFill end
				if value == nil then value = false end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.backdrop = cfg.health.backdrop or {}
				cfg.health.backdrop.clampToFill = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthBackdropClampToFill", cfg.health.backdrop.clampToFill, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.backdrop and hc.backdrop.enabled ~= false
			end,
		},
		{
			name = L["Backdrop color"] or "Backdrop color",
			kind = SettingType.Color,
			field = "healthBackdropColor",
			parentId = "health",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.backdrop and DEFAULTS[kind].health.backdrop.color) or { 0, 0, 0, 0.6 },
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.backdrop and DEFAULTS[kind].health.backdrop.color) or { 0, 0, 0, 0.6 }
				local r, g, b, a = unpackColor(hc.backdrop and hc.backdrop.color, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.health = cfg.health or {}
				cfg.health.backdrop = cfg.health.backdrop or {}
				cfg.health.backdrop.color = { value.r or 0, value.g or 0, value.b or 0, value.a or 0.6 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healthBackdropColor", cfg.health.backdrop.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.backdrop and hc.backdrop.enabled ~= false
			end,
		},
		{
			name = L["Incoming heals"] or "Incoming heals",
			kind = SettingType.Collapsible,
			id = "incomingheal",
			defaultCollapsed = true,
		},
		{
			name = L["Show incoming heal bar"] or "Show incoming heal bar",
			kind = SettingType.Checkbox,
			field = "incomingHealEnabled",
			parentId = "incomingheal",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.incomingHealEnabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.incomingHealEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "incomingHealEnabled", cfg.health.incomingHealEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show sample incoming heals"] or "Show sample incoming heals",
			kind = SettingType.Checkbox,
			field = "incomingHealSample",
			parentId = "incomingheal",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.showSampleIncomingHeal == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.showSampleIncomingHeal = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "incomingHealSample", cfg.health.showSampleIncomingHeal, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.incomingHealEnabled == true
			end,
		},
		{
			name = L["Incoming heal texture"] or "Incoming heal texture",
			kind = SettingType.Dropdown,
			field = "incomingHealTexture",
			parentId = "incomingheal",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				return hc.incomingHealTexture or hc.texture or def.incomingHealTexture or def.texture or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.incomingHealTexture = value or "DEFAULT"
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						local def = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
						return (hc.incomingHealTexture or hc.texture or def.incomingHealTexture or def.texture or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.incomingHealTexture = option.value
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.incomingHealEnabled == true
			end,
		},
		{
			name = L["Incoming heal color"] or "Incoming heal color",
			kind = SettingType.Color,
			field = "incomingHealColor",
			parentId = "incomingheal",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.incomingHealColor) or { 0.2, 0.85, 0.35, 0.45 },
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.incomingHealColor) or { 0.2, 0.85, 0.35, 0.45 }
				local r, g, b, a = unpackColor(hc.incomingHealColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.health = cfg.health or {}
				cfg.health.incomingHealColor = { value.r or 0.2, value.g or 0.85, value.b or 0.35, value.a or 0.45 }
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.incomingHealEnabled == true
			end,
		},
		{
			name = L["Absorb"] or "Absorb",
			kind = SettingType.Collapsible,
			id = "absorb",
			defaultCollapsed = true,
		},
		{
			name = L["Show absorb bar"] or "Show absorb bar",
			kind = SettingType.Checkbox,
			field = "absorbEnabled",
			parentId = "absorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.absorbEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbEnabled", cfg.health.absorbEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show sample absorb"] or "Show sample absorb",
			kind = SettingType.Checkbox,
			field = "absorbSample",
			parentId = "absorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.showSampleAbsorb == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.showSampleAbsorb = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbSample", cfg.health.showSampleAbsorb, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
		},
		{
			name = L["Absorb texture"] or "Absorb texture",
			kind = SettingType.Dropdown,
			field = "absorbTexture",
			parentId = "absorb",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbTexture or "SOLID"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.absorbTexture = value or "SOLID"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbTexture", cfg.health.absorbTexture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.absorbTexture or "SOLID") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.absorbTexture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
		},
		{
			name = L["Absorb reverse fill"] or "Absorb reverse fill",
			kind = SettingType.Checkbox,
			field = "absorbReverse",
			parentId = "absorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local value = hc.absorbReverseFill
				if value == nil then value = defH.absorbReverseFill == true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.absorbReverseFill = value and true or false
				if not cfg.health.absorbReverseFill then
					cfg.health.absorbDontOverflowHealthBar = false
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbDontOverflow", false, nil, true) end
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbReverse", cfg.health.absorbReverseFill, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
		},
		{
			name = L["Don't overflow health bar"] or "Don't overflow health bar",
			kind = SettingType.Checkbox,
			field = "absorbDontOverflow",
			parentId = "absorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local reverseAbsorb = hc.absorbReverseFill
				if reverseAbsorb == nil then reverseAbsorb = defH.absorbReverseFill == true end
				local value = hc.absorbDontOverflowHealthBar
				if value == nil then value = defH.absorbDontOverflowHealthBar == true end
				return value == true and reverseAbsorb == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local reverseAbsorb = cfg.health.absorbReverseFill
				if reverseAbsorb == nil then reverseAbsorb = defH.absorbReverseFill == true end
				cfg.health.absorbDontOverflowHealthBar = (value and reverseAbsorb == true) and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbDontOverflow", cfg.health.absorbDontOverflowHealthBar, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local reverseAbsorb = hc.absorbReverseFill
				if reverseAbsorb == nil then reverseAbsorb = defH.absorbReverseFill == true end
				return hc.absorbEnabled ~= false and reverseAbsorb == true
			end,
		},
		{
			name = L["Absorb overlay height"] or "Absorb overlay height",
			kind = SettingType.Slider,
			allowInput = true,
			field = "absorbOverlayHeight",
			parentId = "absorb",
			minValue = 0,
			maxValue = 300,
			valueStep = 1,
			formatter = GF.FormatOverlayHeight,
			default = 0,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] or {}
				local defH = def.health or {}
				local fallback = GF._computeOverlayHeightFallback((cfg and cfg.height) or def.height, (cfg and cfg.powerHeight) or def.powerHeight)
				local value = hc.absorbOverlayHeight
				if value == nil then value = defH.absorbOverlayHeight end
				return GF._resolveOverlayHeightSetting(value, fallback)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				local def = DEFAULTS[kind] or {}
				local fallback = GF._computeOverlayHeightFallback((cfg and cfg.height) or def.height, (cfg and cfg.powerHeight) or def.powerHeight)
				local v = clampNumber(value, 0, 300, 0)
				if not v or v <= 0 then v = 0 end
				v = GF._resolveOverlayHeightSetting(v, fallback)
				cfg.health.absorbOverlayHeight = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbOverlayHeight", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
		},
		{
			name = L["Anchor overlay to top"] or "Anchor overlay to top",
			kind = SettingType.Checkbox,
			field = "absorbOverlayAnchorTop",
			parentId = "absorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local value = hc.absorbOverlayAnchorTop
				if value == nil then value = defH.absorbOverlayAnchorTop == true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.absorbOverlayAnchorTop = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbOverlayAnchorTop", cfg.health.absorbOverlayAnchorTop, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
		},
		{
			name = L["Custom absorb color"] or "Custom absorb color",
			kind = SettingType.Checkbox,
			field = "absorbUseCustomColor",
			parentId = "absorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbUseCustomColor == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.absorbUseCustomColor = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbUseCustomColor", cfg.health.absorbUseCustomColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
		},
		{
			name = L["Absorb color"] or "Absorb color",
			kind = SettingType.Color,
			field = "absorbColor",
			parentId = "absorb",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.absorbColor) or { 0.85, 0.95, 1, 0.7 },
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.absorbColor) or { 0.85, 0.95, 1, 0.7 }
				local r, g, b, a = unpackColor(hc.absorbColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.health = cfg.health or {}
				cfg.health.absorbColor = { value.r or 0.85, value.g or 0.95, value.b or 1, value.a or 0.7 }
				cfg.health.absorbUseCustomColor = true
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbColor", cfg.health.absorbColor, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbUseCustomColor", true, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false and hc.absorbUseCustomColor == true
			end,
		},
		{
			name = L["Use absorb glow"] or "Use absorb glow",
			kind = SettingType.Checkbox,
			field = "absorbGlow",
			parentId = "absorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local value = hc.useAbsorbGlow
				if value == nil then value = defH.useAbsorbGlow == true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.useAbsorbGlow = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "absorbGlow", cfg.health.useAbsorbGlow, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.absorbEnabled ~= false
			end,
		},
		{
			name = L["Heal absorb"] or "Heal absorb",
			kind = SettingType.Collapsible,
			id = "healabsorb",
			defaultCollapsed = true,
		},
		{
			name = L["Show heal absorb bar"] or "Show heal absorb bar",
			kind = SettingType.Checkbox,
			field = "healAbsorbEnabled",
			parentId = "healabsorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.healAbsorbEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbEnabled", cfg.health.healAbsorbEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show sample heal absorb"] or "Show sample heal absorb",
			kind = SettingType.Checkbox,
			field = "healAbsorbSample",
			parentId = "healabsorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.showSampleHealAbsorb == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.showSampleHealAbsorb = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbSample", cfg.health.showSampleHealAbsorb, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false
			end,
		},
		{
			name = L["Heal absorb texture"] or "Heal absorb texture",
			kind = SettingType.Dropdown,
			field = "healAbsorbTexture",
			parentId = "healabsorb",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbTexture or "SOLID"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.healAbsorbTexture = value or "SOLID"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbTexture", cfg.health.healAbsorbTexture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local hc = cfg and cfg.health or {}
						return (hc.healAbsorbTexture or "SOLID") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.health = cfg.health or {}
						cfg.health.healAbsorbTexture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false
			end,
		},
		{
			name = L["Heal absorb reverse fill"] or "Heal absorb reverse fill",
			kind = SettingType.Checkbox,
			field = "healAbsorbReverse",
			parentId = "healabsorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local value = hc.healAbsorbReverseFill
				if value == nil then value = defH.healAbsorbReverseFill == true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.healAbsorbReverseFill = value and true or false
				if cfg.health.healAbsorbReverseFill then
					cfg.health.healAbsorbDontOverflowHealthBar = false
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbDontOverflow", false, nil, true) end
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbReverse", cfg.health.healAbsorbReverseFill, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false
			end,
		},
		{
			name = L["Don't overflow health bar"] or "Don't overflow health bar",
			kind = SettingType.Checkbox,
			field = "healAbsorbDontOverflow",
			parentId = "healabsorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local reverseHealAbsorb = hc.healAbsorbReverseFill
				if reverseHealAbsorb == nil then reverseHealAbsorb = defH.healAbsorbReverseFill == true end
				local value = hc.healAbsorbDontOverflowHealthBar
				if value == nil then value = defH.healAbsorbDontOverflowHealthBar ~= false end
				return value == true and reverseHealAbsorb ~= true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local reverseHealAbsorb = cfg.health.healAbsorbReverseFill
				if reverseHealAbsorb == nil then reverseHealAbsorb = defH.healAbsorbReverseFill == true end
				cfg.health.healAbsorbDontOverflowHealthBar = (value and reverseHealAbsorb ~= true) and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbDontOverflow", cfg.health.healAbsorbDontOverflowHealthBar, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local reverseHealAbsorb = hc.healAbsorbReverseFill
				if reverseHealAbsorb == nil then reverseHealAbsorb = defH.healAbsorbReverseFill == true end
				return hc.healAbsorbEnabled ~= false and reverseHealAbsorb ~= true
			end,
		},
		{
			name = L["Heal absorb overlay height"] or "Heal absorb overlay height",
			kind = SettingType.Slider,
			allowInput = true,
			field = "healAbsorbOverlayHeight",
			parentId = "healabsorb",
			minValue = 0,
			maxValue = 300,
			valueStep = 1,
			formatter = GF.FormatOverlayHeight,
			default = 0,
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = DEFAULTS[kind] or {}
				local defH = def.health or {}
				local fallback = GF._computeOverlayHeightFallback((cfg and cfg.height) or def.height, (cfg and cfg.powerHeight) or def.powerHeight)
				local value = hc.healAbsorbOverlayHeight
				if value == nil then value = defH.healAbsorbOverlayHeight end
				return GF._resolveOverlayHeightSetting(value, fallback)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				local def = DEFAULTS[kind] or {}
				local fallback = GF._computeOverlayHeightFallback((cfg and cfg.height) or def.height, (cfg and cfg.powerHeight) or def.powerHeight)
				local v = clampNumber(value, 0, 300, 0)
				if not v or v <= 0 then v = 0 end
				v = GF._resolveOverlayHeightSetting(v, fallback)
				cfg.health.healAbsorbOverlayHeight = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbOverlayHeight", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false
			end,
		},
		{
			name = L["Anchor overlay to top"] or "Anchor overlay to top",
			kind = SettingType.Checkbox,
			field = "healAbsorbOverlayAnchorTop",
			parentId = "healabsorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				local value = hc.healAbsorbOverlayAnchorTop
				if value == nil then value = defH.healAbsorbOverlayAnchorTop == true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.healAbsorbOverlayAnchorTop = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbOverlayAnchorTop", cfg.health.healAbsorbOverlayAnchorTop, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false
			end,
		},
		{
			name = L["Custom heal absorb color"] or "Custom heal absorb color",
			kind = SettingType.Checkbox,
			field = "healAbsorbUseCustomColor",
			parentId = "healabsorb",
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbUseCustomColor == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.health = cfg.health or {}
				cfg.health.healAbsorbUseCustomColor = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbUseCustomColor", cfg.health.healAbsorbUseCustomColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false
			end,
		},
		{
			name = L["Heal absorb color"] or "Heal absorb color",
			kind = SettingType.Color,
			field = "healAbsorbColor",
			parentId = "healabsorb",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.healAbsorbColor) or { 1, 0.3, 0.3, 0.7 },
			get = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.healAbsorbColor) or { 1, 0.3, 0.3, 0.7 }
				local r, g, b, a = unpackColor(hc.healAbsorbColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.health = cfg.health or {}
				cfg.health.healAbsorbColor = { value.r or 1, value.g or 0.3, value.b or 0.3, value.a or 0.7 }
				cfg.health.healAbsorbUseCustomColor = true
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbColor", cfg.health.healAbsorbColor, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "healAbsorbUseCustomColor", true, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local hc = cfg and cfg.health or {}
				return hc.healAbsorbEnabled ~= false and hc.healAbsorbUseCustomColor == true
			end,
		},
		{
			name = L["Level"] or "Level",
			kind = SettingType.Collapsible,
			id = "level",
			defaultCollapsed = true,
		},
		{
			name = L["Show level"] or "Show level",
			kind = SettingType.Checkbox,
			field = "levelEnabled",
			parentId = "level",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelEnabled", cfg.status.levelEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Hide level at max"] or "Hide level at max",
			kind = SettingType.Checkbox,
			field = "hideLevelAtMax",
			parentId = "level",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.hideLevelAtMax == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.hideLevelAtMax = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "hideLevelAtMax", cfg.status.hideLevelAtMax, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Level class color"] or "Level class color",
			kind = SettingType.Checkbox,
			field = "levelClassColor",
			parentId = "level",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return (sc.levelColorMode or "CUSTOM") == "CLASS"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelColorMode = value and "CLASS" or "CUSTOM"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelClassColor", value and true or false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Level color"] or "Level color",
			kind = SettingType.Color,
			field = "levelColor",
			parentId = "level",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.levelColor) or { 1, 0.85, 0, 1 },
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.levelColor) or { 1, 0.85, 0, 1 }
				local r, g, b, a = unpackColor(sc.levelColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.status = cfg.status or {}
				cfg.status.levelColor = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				cfg.status.levelColorMode = "CUSTOM"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelColor", cfg.status.levelColor, nil, true) end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelClassColor", false, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false and (sc.levelColorMode or "CUSTOM") == "CUSTOM"
			end,
		},
		{
			name = L["Level font size"] or "Level font size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "levelFontSize",
			parentId = "level",
			minValue = 8,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local tc = cfg and cfg.text or {}
				local hc = cfg and cfg.health or {}
				return sc.levelFontSize or tc.fontSize or hc.fontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelFontSize = clampNumber(value, 8, 30, cfg.status.levelFontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelFontSize", cfg.status.levelFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Level font"] or "Level font",
			kind = SettingType.Dropdown,
			field = "levelFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "level",
			default = GF.GlobalFontConfigKey(),
			customDefaultText = (function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return GF.DropdownOptionLabel(GF.FontOptionsWithDefault, sc.levelFont or GF.GlobalFontConfigKey(), DEFAULT or "Default")
			end)(),
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelFont or GF.GlobalFontConfigKey()
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelFont = (value ~= nil and value ~= "") and value or GF.GlobalFontConfigKey()
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelFont", cfg.status.levelFont, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = GF.DropdownRadioGenerator(GF.FontOptionsWithDefault),
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Level font outline"] or "Level font outline",
			kind = SettingType.Dropdown,
			field = "levelFontOutline",
			parentId = "level",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local tc = cfg and cfg.text or {}
				local hc = cfg and cfg.health or {}
				return normalizeFontStyleChoice(sc.levelFontOutline, tc.fontOutline or hc.fontOutline or "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local tc = cfg and cfg.text or {}
				local hc = cfg and cfg.health or {}
				cfg.status = cfg.status or {}
				cfg.status.levelFontOutline = normalizeFontStyleChoice(value, tc.fontOutline or hc.fontOutline or "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelFontOutline", cfg.status.levelFontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local sc = cfg and cfg.status or {}
						local tc = cfg and cfg.text or {}
						local hc = cfg and cfg.health or {}
						return normalizeFontStyleChoice(sc.levelFontOutline, tc.fontOutline or hc.fontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local tc = cfg and cfg.text or {}
						local hc = cfg and cfg.health or {}
						cfg.status = cfg.status or {}
						cfg.status.levelFontOutline = normalizeFontStyleChoice(option.value, tc.fontOutline or hc.fontOutline or "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelFontOutline", cfg.status.levelFontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Level anchor"] or "Level anchor",
			kind = SettingType.Dropdown,
			field = "levelAnchor",
			parentId = "level",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelAnchor or "RIGHT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = string.format("%s (%s)", L["Frame strata"] or "Frame strata", LEVEL or "Level"),
			kind = SettingType.Dropdown,
			field = "levelStrata",
			parentId = "level",
			default = "",
			customDefaultText = (function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return GF.DropdownOptionLabel(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT, GF.NormalizeFrameStrataToken(sc.levelStrata) or "", DEFAULT or "Default")
			end)(),
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return GF.NormalizeFrameStrataToken(sc.levelStrata) or ""
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelStrata = GF.NormalizeFrameStrataToken(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelStrata", cfg.status.levelStrata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = GF.DropdownRadioGenerator(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT),
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = string.format("%s (%s)", L["UFFrameLevel"] or "Frame level", LEVEL or "Level"),
			kind = SettingType.Slider,
			allowInput = true,
			field = "levelFrameLevelOffset",
			parentId = "level",
			minValue = -20,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelFrameLevelOffset or 5
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelFrameLevelOffset = clampNumber(value, -20, 50, cfg.status.levelFrameLevelOffset or 5)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelFrameLevelOffset", cfg.status.levelFrameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Level offset X"] or "Level offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "levelOffsetX",
			parentId = "level",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return (sc.levelOffset and sc.levelOffset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelOffset = cfg.status.levelOffset or {}
				cfg.status.levelOffset.x = clampNumber(value, -200, 200, (cfg.status.levelOffset and cfg.status.levelOffset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelOffsetX", cfg.status.levelOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Level offset Y"] or "Level offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "levelOffsetY",
			parentId = "level",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return (sc.levelOffset and sc.levelOffset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.levelOffset = cfg.status.levelOffset or {}
				cfg.status.levelOffset.y = clampNumber(value, -200, 200, (cfg.status.levelOffset and cfg.status.levelOffset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "levelOffsetY", cfg.status.levelOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				return sc.levelEnabled ~= false
			end,
		},
		{
			name = L["Status text"] or "Status text",
			kind = SettingType.Collapsible,
			id = "statustext",
			defaultCollapsed = true,
		},
		{
			name = L["Show status text"] or "Show status text",
			kind = SettingType.Checkbox,
			field = "statusTextEnabled",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextEnabled", cfg.status.unitStatus.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show offline"] or "Show offline",
			kind = SettingType.Checkbox,
			field = "statusTextShowOffline",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus or {}
				if us.showOffline == nil then return def.showOffline ~= false end
				return us.showOffline == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.showOffline = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextShowOffline", cfg.status.unitStatus.showOffline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Show AFK"] or "Show AFK",
			kind = SettingType.Checkbox,
			field = "statusTextShowAFK",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus or {}
				if us.showAFK == nil then return def.showAFK == true end
				return us.showAFK == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.showAFK = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextShowAFK", cfg.status.unitStatus.showAFK, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Show dead"] or "Show dead",
			kind = SettingType.Checkbox,
			field = "statusTextShowDead",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus or {}
				if us.showDead == nil then return def.showDead ~= false end
				return us.showDead == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.showDead = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextShowDead", cfg.status.unitStatus.showDead, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Show DND"] or "Show DND",
			kind = SettingType.Checkbox,
			field = "statusTextShowDND",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus or {}
				if us.showDND == nil then return def.showDND == true end
				return us.showDND == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.showDND = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextShowDND", cfg.status.unitStatus.showDND, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Show ghost"] or "Show ghost",
			kind = SettingType.Checkbox,
			field = "statusTextShowGhost",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus or {}
				if us.showGhost == nil then return def.showGhost ~= false end
				return us.showGhost == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.showGhost = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextShowGhost", cfg.status.unitStatus.showGhost, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Hide health text when offline"] or "Hide health text when offline",
			kind = SettingType.Checkbox,
			field = "statusTextHideHealthTextOffline",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus or {}
				if us.hideHealthTextWhenOffline == nil then return def.hideHealthTextWhenOffline == true end
				return us.hideHealthTextWhenOffline == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.hideHealthTextWhenOffline = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextHideHealthTextOffline", cfg.status.unitStatus.hideHealthTextWhenOffline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = COLOR,
			kind = SettingType.Color,
			field = "statusTextColor",
			parentId = "statustext",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus and DEFAULTS[kind].status.unitStatus.color) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.unitStatus and DEFAULTS[kind].status.unitStatus.color) or { 1, 1, 1, 1 }
				local r, g, b, a = unpackColor(us.color, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.color = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextColor", cfg.status.unitStatus.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = FONT_SIZE,
			kind = SettingType.Slider,
			allowInput = true,
			field = "statusTextFontSize",
			parentId = "statustext",
			minValue = 8,
			maxValue = 100,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				return us.fontSize or hc.fontSize or defH.fontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.fontSize = clampNumber(value, 8, 100, cfg.status.unitStatus.fontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextFontSize", cfg.status.unitStatus.fontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Font"] or "Font",
			kind = SettingType.Dropdown,
			field = "statusTextFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				return us.font or hc.font or defH.font or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.font = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local sc = cfg and cfg.status or {}
						local us = sc.unitStatus or {}
						local hc = cfg and cfg.health or {}
						local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
						return (us.font or hc.font or defH.font or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.status = cfg.status or {}
						cfg.status.unitStatus = cfg.status.unitStatus or {}
						cfg.status.unitStatus.font = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Font outline"] or "Font outline",
			kind = SettingType.Dropdown,
			field = "statusTextFontOutline",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				return normalizeFontStyleChoice(us.fontOutline, hc.fontOutline or defH.fontOutline or "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local hc = cfg and cfg.health or {}
				local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.fontOutline = normalizeFontStyleChoice(value, hc.fontOutline or defH.fontOutline or "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextFontOutline", cfg.status.unitStatus.fontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local sc = cfg and cfg.status or {}
						local us = sc.unitStatus or {}
						local hc = cfg and cfg.health or {}
						local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
						return normalizeFontStyleChoice(us.fontOutline, hc.fontOutline or defH.fontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local hc = cfg and cfg.health or {}
						local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
						cfg.status = cfg.status or {}
						cfg.status.unitStatus = cfg.status.unitStatus or {}
						cfg.status.unitStatus.fontOutline = normalizeFontStyleChoice(option.value, hc.fontOutline or defH.fontOutline or "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextFontOutline", cfg.status.unitStatus.fontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Anchor"] or "Anchor",
			kind = SettingType.Dropdown,
			field = "statusTextAnchor",
			parentId = "statustext",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.anchor or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.anchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Offset X"] or "Offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "statusTextOffsetX",
			parentId = "statustext",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return (us.offset and us.offset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.offset = cfg.status.unitStatus.offset or {}
				cfg.status.unitStatus.offset.x = clampNumber(value, -200, 200, (cfg.status.unitStatus.offset and cfg.status.unitStatus.offset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextOffsetX", cfg.status.unitStatus.offset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = L["Offset Y"] or "Offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "statusTextOffsetY",
			parentId = "statustext",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return (us.offset and us.offset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.offset = cfg.status.unitStatus.offset or {}
				cfg.status.unitStatus.offset.y = clampNumber(value, -200, 200, (cfg.status.unitStatus.offset and cfg.status.unitStatus.offset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextOffsetY", cfg.status.unitStatus.offset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local us = sc.unitStatus or {}
				return us.enabled ~= false
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "statustext",
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Show"] or "Show",
			kind = SettingType.Checkbox,
			field = "statusTextShowGroup",
			parentId = "statustext",
			get = function() return getGroupNumberEnabledValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.enabled = value and true or false
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.showGroup = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextShowGroup", cfg.status.groupNumber.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isStatusTextEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Format"] or "Format",
			kind = SettingType.Dropdown,
			field = "statusTextGroupFormat",
			parentId = "statustext",
			customDefaultText = getGroupFormatLabel(),
			get = function() return getGroupFormatValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.format = normalizeGroupNumberFormat(value) or "GROUP"
				cfg.status.unitStatus = cfg.status.unitStatus or {}
				cfg.status.unitStatus.groupFormat = cfg.status.groupNumber.format
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextGroupFormat", cfg.status.groupNumber.format, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root, data)
				for _, option in ipairs(GF._groupNumberFormatCache.opts) do
					local value = option and option.value
					if value ~= nil then
						local label = option.label or option.text or tostring(value)
						root:CreateRadio(label, function() return getGroupFormatValue() == value end, function()
							local cfg = getCfg(kind)
							if not cfg then return end
							cfg.status = cfg.status or {}
							cfg.status.groupNumber = cfg.status.groupNumber or {}
							cfg.status.groupNumber.format = value
							cfg.status.unitStatus = cfg.status.unitStatus or {}
							cfg.status.unitStatus.groupFormat = value
							if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "statusTextGroupFormat", value, nil, true) end
							data.customDefaultText = label
							if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
							GF:ApplyHeaderAttributes(kind)
						end)
					end
				end
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = COLOR,
			kind = SettingType.Color,
			field = "groupNumberColor",
			parentId = "statustext",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.groupNumber and DEFAULTS[kind].status.groupNumber.color) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
				local r, g, b, a = unpackColor(style.color, GFH.COLOR_WHITE)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.color = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberColor", cfg.status.groupNumber.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = FONT_SIZE,
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupNumberFontSize",
			parentId = "statustext",
			minValue = 8,
			maxValue = 100,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
				return style.fontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.fontSize = clampNumber(value, 8, 100, cfg.status.groupNumber.fontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberFontSize", cfg.status.groupNumber.fontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Font"] or "Font",
			kind = SettingType.Dropdown,
			field = "groupNumberFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
				return style.font or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.font = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local def = DEFAULTS[kind] or {}
						local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
						return (style.font or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.status = cfg.status or {}
						cfg.status.groupNumber = cfg.status.groupNumber or {}
						cfg.status.groupNumber.font = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Font outline"] or "Font outline",
			kind = SettingType.Dropdown,
			field = "groupNumberFontOutline",
			parentId = "statustext",
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
				return normalizeFontStyleChoice(style.fontOutline, "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.fontOutline = normalizeFontStyleChoice(value, "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberFontOutline", cfg.status.groupNumber.fontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local def = DEFAULTS[kind] or {}
						local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
						return normalizeFontStyleChoice(style.fontOutline, "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.status = cfg.status or {}
						cfg.status.groupNumber = cfg.status.groupNumber or {}
						cfg.status.groupNumber.fontOutline = normalizeFontStyleChoice(option.value, "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberFontOutline", cfg.status.groupNumber.fontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Anchor"] or "Anchor",
			kind = SettingType.Dropdown,
			field = "groupNumberAnchor",
			parentId = "statustext",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
				return style.anchor or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.anchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Offset X"] or "Offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupNumberOffsetX",
			parentId = "statustext",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
				local off = style.offset or {}
				return off.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.offset = cfg.status.groupNumber.offset or {}
				cfg.status.groupNumber.offset.x = clampNumber(value, -200, 200, (cfg.status.groupNumber.offset and cfg.status.groupNumber.offset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberOffsetX", cfg.status.groupNumber.offset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Offset Y"] or "Offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupNumberOffsetY",
			parentId = "statustext",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupNumberStyle(cfg, def, (cfg and cfg.health) or {})
				local off = style.offset or {}
				return off.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.groupNumber = cfg.status.groupNumber or {}
				cfg.status.groupNumber.offset = cfg.status.groupNumber.offset or {}
				cfg.status.groupNumber.offset.y = clampNumber(value, -200, 200, (cfg.status.groupNumber.offset and cfg.status.groupNumber.offset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupNumberOffsetY", cfg.status.groupNumber.offset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupNumberSettingsEnabled() end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["UFRangeFade"] or "Range fade",
			kind = SettingType.Collapsible,
			id = "rangeFade",
			defaultCollapsed = true,
		},
		{
			name = L["UFRangeFadeEnable"] or "Enable range fade",
			kind = SettingType.Checkbox,
			field = "rangeFadeEnabled",
			parentId = "rangeFade",
			get = function() return isRangeFadeEnabled() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.rangeFade = cfg.status.rangeFade or {}
				cfg.status.rangeFade.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "rangeFadeEnabled", cfg.status.rangeFade.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRangeFade()
			end,
		},
		{
			name = L["UFRangeFadeAlpha"] or "Out of range opacity",
			kind = SettingType.Slider,
			allowInput = true,
			field = "rangeFadeAlpha",
			parentId = "rangeFade",
			minValue = 0,
			maxValue = 1,
			valueStep = 0.01,
			formatter = formatSliderDecimal,
			get = function()
				local rf, def = getRangeFadeConfig()
				local value = rf.alpha
				if value == nil then value = def.alpha end
				if value == nil then value = 0.55 end
				return value
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.rangeFade = cfg.status.rangeFade or {}
				local current = cfg.status.rangeFade.alpha
				if current == nil then
					local _, def = getRangeFadeConfig()
					current = def.alpha
				end
				if current == nil then current = 0.55 end
				cfg.status.rangeFade.alpha = clampNumber(value, 0, 1, current)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "rangeFadeAlpha", cfg.status.rangeFade.alpha, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRangeFade()
			end,
			isEnabled = function() return isRangeFadeEnabled() end,
		},
		{
			name = rangeFadeOfflineOpacityLabel,
			kind = SettingType.Slider,
			allowInput = true,
			field = "rangeFadeOfflineAlpha",
			parentId = "rangeFade",
			minValue = 0,
			maxValue = 1,
			valueStep = 0.01,
			formatter = formatSliderDecimal,
			get = function()
				local rf, def = getRangeFadeConfig()
				local value = rf.offlineAlpha
				if value == nil then value = def.offlineAlpha end
				if value == nil then value = rf.alpha end
				if value == nil then value = def.alpha end
				if value == nil then value = 0.4 end
				return value
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.rangeFade = cfg.status.rangeFade or {}
				local current = cfg.status.rangeFade.offlineAlpha
				if current == nil then
					local rf, def = getRangeFadeConfig()
					current = rf.alpha
					if current == nil then current = def.offlineAlpha end
					if current == nil then current = def.alpha end
				end
				if current == nil then current = 0.4 end
				cfg.status.rangeFade.offlineAlpha = clampNumber(value, 0, 1, current)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "rangeFadeOfflineAlpha", cfg.status.rangeFade.offlineAlpha, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRangeFade()
			end,
			isEnabled = function() return isRangeFadeEnabled() end,
		},
		{
			name = L["UFDispelIndicator"] or "Dispel indicator",
			kind = SettingType.Collapsible,
			id = "dispeltint",
			defaultCollapsed = true,
		},
		{
			name = L["Enable overlay"] or "Enable overlay",
			kind = SettingType.Checkbox,
			field = "dispelTintEnabled",
			parentId = "dispeltint",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				if dt.enabled == nil then return def.enabled ~= false end
				return dt.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintEnabled", cfg.status.dispelTint.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "dispeltint",
			isShown = function() return not GF.IsBlizzardAuraRenderTypeEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY, "dispels") end,
		},
		{
			name = (L["Blizzard"] or "Blizzard") .. " " .. (L["UFAuraRendererBlizzardDispelOverlay"] or "Dispel overlay"),
			kind = SettingType.Checkbox,
			field = "dispelTintBlizzardPrivateAuraDispels",
			parentId = "dispeltint",
			get = function()
				return GF.ResolveBlizzardPrivateAuraDispelsEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.blizzardPrivateAuraDispels = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintBlizzardPrivateAuraDispels", cfg.status.dispelTint.blizzardPrivateAuraDispels, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			isShown = function() return not GF.IsBlizzardAuraRenderTypeEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY, "dispels") end,
		},
		{
			name = L["UFAuraRendererBlizzardDispelMode"] or "Dispel indicator mode",
			kind = SettingType.Dropdown,
			field = "dispelTintBlizzardDispelMode",
			parentId = "dispeltint",
			values = GF.blizzardDispelIndicatorModeOptions,
			tooltip = L["UFAuraRendererBlizzardDispelModeTooltip"] or "Only affects the Blizzard dispel indicator for private aura dispels.",
			customDefaultText = GF.DropdownOptionLabel(GF.blizzardDispelIndicatorModeOptions, GF.GetDispelTintBlizzardDispelIndicatorMode(getCfg(kind), DEFAULTS[kind] or EMPTY), L["UFAuraRendererBlizzardDispelModeAll"] or "All"),
			get = function() return GF.GetDispelTintBlizzardDispelIndicatorMode(getCfg(kind), DEFAULTS[kind] or EMPTY) end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.blizzardDispelIndicatorMode = GF.NormalizeBlizzardDispelIndicatorMode(value) or GF.BLIZZARD_DISPEL_MODE_ALL
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintBlizzardDispelMode", cfg.status.dispelTint.blizzardDispelIndicatorMode, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			generator = GF.DropdownRadioGenerator(GF.blizzardDispelIndicatorModeOptions),
			isEnabled = function() return GF.ResolveBlizzardPrivateAuraDispelsEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY) end,
			isShown = function() return not GF.IsBlizzardAuraRenderTypeEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY, "dispels") end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "dispeltint",
			isShown = function() return not GF.IsBlizzardAuraRenderTypeEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY, "dispels") end,
		},
		{
			name = L["Background color change"] or "Background color change",
			kind = SettingType.Checkbox,
			field = "dispelTintFillEnabled",
			parentId = "dispeltint",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				if dt.fillEnabled == nil then return def.fillEnabled ~= false end
				return dt.fillEnabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.fillEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintFillEnabled", cfg.status.dispelTint.fillEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			isEnabled = function() return isDispelTintEnabled() end,
		},
		{
			name = "Dispel indicator strata",
			kind = SettingType.Dropdown,
			field = "dispelTintStrata",
			parentId = "dispeltint",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				return GF.NormalizeFrameStrataToken(dt.strata) or ""
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.strata = GF.NormalizeFrameStrataToken(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintStrata", cfg.status.dispelTint.strata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			getValueText = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				return GF.DropdownOptionLabel(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT, GF.NormalizeFrameStrataToken(dt.strata) or "", DEFAULT or "Default")
			end,
			generator = GF.DropdownRadioGenerator(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT),
			isEnabled = isDispelTintEnabled,
		},
		{
			name = "Dispel indicator level",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintFrameLevelOffset",
			parentId = "dispeltint",
			minValue = -20,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local value = dt.frameLevelOffset
				if value == nil then value = 1 end
				value = clampNumber(value, -20, 1000, 1)
				return floor(value + (value >= 0 and 0.5 or -0.5))
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				local levelOffset = clampNumber(value, -20, 1000, cfg.status.dispelTint.frameLevelOffset or 1)
				levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
				cfg.status.dispelTint.frameLevelOffset = levelOffset
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintFrameLevelOffset", cfg.status.dispelTint.frameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDispelTintEnabled,
		},
		{
			name = L["Background color"] or "Background color",
			kind = SettingType.Color,
			field = "dispelTintFillColor",
			parentId = "dispeltint",
			hasOpacity = false,
			default = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint and DEFAULTS[kind].status.dispelTint.fillColor) or { 0, 0, 0, 1 },
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint and DEFAULTS[kind].status.dispelTint.fillColor) or { 0, 0, 0, 1 }
				local r, g, b, a = unpackColor(dt.fillColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.fillColor = { value.r or 0, value.g or 0, value.b or 0, 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintFillColor", cfg.status.dispelTint.fillColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local enabled = dt.enabled
				if enabled == nil then enabled = def.enabled ~= false end
				local fillEnabled = dt.fillEnabled
				if fillEnabled == nil then fillEnabled = def.fillEnabled ~= false end
				return enabled and fillEnabled
			end,
		},
		{
			name = L["Background alpha"] or "Background alpha",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintFillAlpha",
			parentId = "dispeltint",
			minValue = 0,
			maxValue = 1,
			valueStep = 0.01,
			formatter = formatSliderDecimal,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.fillAlpha or def.fillAlpha or 0.2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.fillAlpha = clampNumber(value, 0, 1, cfg.status.dispelTint.fillAlpha or 0.2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintFillAlpha", cfg.status.dispelTint.fillAlpha, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local enabled = dt.enabled
				if enabled == nil then enabled = def.enabled ~= false end
				local fillEnabled = dt.fillEnabled
				if fillEnabled == nil then fillEnabled = def.fillEnabled ~= false end
				return enabled and fillEnabled
			end,
		},
		{
			name = L["Tint alpha"] or "Tint alpha",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintAlpha",
			parentId = "dispeltint",
			minValue = 0,
			maxValue = 1,
			valueStep = 0.01,
			formatter = formatSliderDecimal,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.alpha or def.alpha or 0.25
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.alpha = clampNumber(value, 0, 1, cfg.status.dispelTint.alpha or 0.25)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintAlpha", cfg.status.dispelTint.alpha, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				if dt.enabled == nil then return def.enabled ~= false end
				return dt.enabled == true
			end,
		},
		{
			name = L["Show sample"] or "Show sample",
			kind = SettingType.Checkbox,
			field = "dispelTintSample",
			parentId = "dispeltint",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				if dt.showSample == nil then return def.showSample == true end
				return dt.showSample == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.showSample = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintSample", cfg.status.dispelTint.showSample, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local overlayEnabled = dt.enabled
				if overlayEnabled == nil then overlayEnabled = def.enabled ~= false end
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return overlayEnabled == true or glowEnabled == true
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "dispeltint",
		},
		{
			name = L["Enable glow"] or "Enable glow",
			kind = SettingType.Checkbox,
			field = "dispelTintGlowEnabled",
			parentId = "dispeltint",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				if dt.glowEnabled == nil then return def.glowEnabled == true end
				return dt.glowEnabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowEnabled", cfg.status.dispelTint.glowEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
		},
		{
			name = L["UFDispelGlowOverOverlay"] or "Glow over overlay",
			kind = SettingType.Checkbox,
			field = "dispelTintGlowOverOverlay",
			parentId = "dispeltint",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				if dt.glowOverOverlay == nil then return def.glowOverOverlay == true end
				return dt.glowOverOverlay == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowOverOverlay = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowOverOverlay", cfg.status.dispelTint.glowOverOverlay, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				if glowEnabled ~= true then return false end
				local overlayEnabled = dt.enabled
				if overlayEnabled == nil then overlayEnabled = def.enabled ~= false end
				return overlayEnabled == true or GF.ResolveBlizzardPrivateAuraDispelsEnabled(cfg, DEFAULTS[kind] or EMPTY)
			end,
		},
		{
			name = L["Glow color"] or "Glow color",
			kind = SettingType.Dropdown,
			field = "dispelTintGlowColorMode",
			parentId = "dispeltint",
			generator = function(_, root, data)
				root:CreateRadio("Dispell color", function() return data.get and data.get() == "DISPEL" end, function()
					if data.set then data.set(nil, "DISPEL") end
					if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
				end)
				root:CreateRadio("Custom color", function() return data.get and data.get() == "CUSTOM" end, function()
					if data.set then data.set(nil, "CUSTOM") end
					if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
				end)
			end,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.glowColorMode or def.glowColorMode or "DISPEL"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowColorMode = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowColorMode", value, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return glowEnabled == true
			end,
		},
		{
			name = L["Custom glow color"] or "Custom glow color",
			kind = SettingType.Color,
			field = "dispelTintGlowColor",
			parentId = "dispeltint",
			hasOpacity = false,
			default = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint and DEFAULTS[kind].status.dispelTint.glowColor) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint and DEFAULTS[kind].status.dispelTint.glowColor) or { 1, 1, 1, 1 }
				local r, g, b, a = unpackColor(dt.glowColor, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowColor = { value.r or 1, value.g or 1, value.b or 1, 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowColor", cfg.status.dispelTint.glowColor, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local mode = dt.glowColorMode or def.glowColorMode or "DISPEL"
				return mode == "CUSTOM"
			end,
		},
		{
			name = L["Glow effect"] or "Glow effect",
			kind = SettingType.Dropdown,
			field = "dispelTintGlowEffect",
			parentId = "dispeltint",
			generator = function(_, root, data)
				local options = {
					{ value = "PIXEL", label = "Pixel" },
					{ value = "SHINE", label = "Shine" },
					{ value = "BLIZZARD", label = "Blizzard" },
				}
				for _, option in ipairs(options) do
					root:CreateRadio(option.label, function() return data.get and data.get() == option.value end, function()
						if data.set then data.set(nil, option.value) end
						if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
					end)
				end
			end,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.glowEffect or def.glowEffect or "PIXEL"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowEffect = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowEffect", value, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return glowEnabled == true
			end,
		},
		{
			name = L["Animation speed"] or "Animation speed",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintGlowFrequency",
			parentId = "dispeltint",
			minValue = -1.5,
			maxValue = 1.5,
			valueStep = 0.25,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.glowFrequency or def.glowFrequency or 0.25
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowFrequency = clampNumber(value, -1.5, 1.5, cfg.status.dispelTint.glowFrequency or 0.25)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowFrequency", cfg.status.dispelTint.glowFrequency, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return glowEnabled == true
			end,
		},
		{
			name = L["X Offset"] or "X Offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintGlowX",
			parentId = "dispeltint",
			minValue = -10,
			maxValue = 10,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.glowX or def.glowX or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowX = clampNumber(value, -10, 10, cfg.status.dispelTint.glowX or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowX", cfg.status.dispelTint.glowX, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return glowEnabled == true
			end,
		},
		{
			name = L["Y Offset"] or "Y Offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintGlowY",
			parentId = "dispeltint",
			minValue = -10,
			maxValue = 10,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.glowY or def.glowY or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowY = clampNumber(value, -10, 10, cfg.status.dispelTint.glowY or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowY", cfg.status.dispelTint.glowY, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return glowEnabled == true
			end,
		},
		{
			name = L["Number of lines"] or "Number of lines",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintGlowLines",
			parentId = "dispeltint",
			minValue = 1,
			maxValue = 20,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.glowLines or def.glowLines or 8
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowLines = clampNumber(value, 1, 20, cfg.status.dispelTint.glowLines or 8)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowLines", cfg.status.dispelTint.glowLines, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return glowEnabled == true
			end,
		},
		{
			name = L["Thickness"] or "Thickness",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dispelTintGlowThickness",
			parentId = "dispeltint",
			minValue = 1,
			maxValue = 10,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				return dt.glowThickness or def.glowThickness or 3
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.dispelTint = cfg.status.dispelTint or {}
				cfg.status.dispelTint.glowThickness = clampNumber(value, 1, 10, cfg.status.dispelTint.glowThickness or 3)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dispelTintGlowThickness", cfg.status.dispelTint.glowThickness, nil, true) end
				GF:RefreshDispelTint()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local dt = sc.dispelTint or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.dispelTint) or {}
				local glowEnabled = dt.glowEnabled
				if glowEnabled == nil then glowEnabled = def.glowEnabled == true end
				return glowEnabled == true
			end,
		},
		{
			name = L["Group icons"] or "Group icons",
			kind = SettingType.Collapsible,
			id = "groupicons",
			defaultCollapsed = true,
		},
		{
			name = L["Show leader icon"] or "Show leader icon",
			kind = SettingType.Checkbox,
			field = "leaderIconEnabled",
			parentId = "groupicons",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.leaderIcon = cfg.status.leaderIcon or {}
				cfg.status.leaderIcon.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "leaderIconEnabled", cfg.status.leaderIcon.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Leader icon size"] or "Leader icon size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "leaderIconSize",
			parentId = "groupicons",
			minValue = 8,
			maxValue = 40,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.size or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.leaderIcon = cfg.status.leaderIcon or {}
				cfg.status.leaderIcon.size = clampNumber(value, 8, 40, cfg.status.leaderIcon.size or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "leaderIconSize", cfg.status.leaderIcon.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.enabled ~= false
			end,
		},
		{
			name = L["Leader icon anchor"] or "Leader icon anchor",
			kind = SettingType.Dropdown,
			field = "leaderIconPoint",
			parentId = "groupicons",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.point or "TOPLEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.leaderIcon = cfg.status.leaderIcon or {}
				cfg.status.leaderIcon.point = value
				cfg.status.leaderIcon.relativePoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "leaderIconPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.enabled ~= false
			end,
		},
		{
			name = L["Leader icon offset X"] or "Leader icon offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "leaderIconOffsetX",
			parentId = "groupicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.leaderIcon = cfg.status.leaderIcon or {}
				cfg.status.leaderIcon.x = clampNumber(value, -200, 200, cfg.status.leaderIcon.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "leaderIconOffsetX", cfg.status.leaderIcon.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.enabled ~= false
			end,
		},
		{
			name = L["Leader icon offset Y"] or "Leader icon offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "leaderIconOffsetY",
			parentId = "groupicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.leaderIcon = cfg.status.leaderIcon or {}
				cfg.status.leaderIcon.y = clampNumber(value, -200, 200, cfg.status.leaderIcon.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "leaderIconOffsetY", cfg.status.leaderIcon.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local lc = sc.leaderIcon or {}
				return lc.enabled ~= false
			end,
		},
		{
			name = COMPACT_UNIT_FRAME_PROFILE_DISPLAYMAINTANKANDASSIST or "Display Main Tank and Assist",
			kind = SettingType.Checkbox,
			field = "raidAssignmentIconEnabled",
			parentId = "groupicons",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				local enabled = racfg.enabled
				if enabled == nil then enabled = def.enabled == true end
				return enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidAssignmentIcon = cfg.status.raidAssignmentIcon or {}
				cfg.status.raidAssignmentIcon.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidAssignmentIconEnabled", cfg.status.raidAssignmentIcon.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = string.format("%s %s", COMPACT_UNIT_FRAME_PROFILE_DISPLAYMAINTANKANDASSIST or "Main Tank & Assist", L["Size"] or "Size"),
			kind = SettingType.Slider,
			allowInput = true,
			field = "raidAssignmentIconSize",
			parentId = "groupicons",
			minValue = 8,
			maxValue = 40,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				return racfg.size or def.size or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidAssignmentIcon = cfg.status.raidAssignmentIcon or {}
				cfg.status.raidAssignmentIcon.size = clampNumber(value, 8, 40, cfg.status.raidAssignmentIcon.size or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidAssignmentIconSize", cfg.status.raidAssignmentIcon.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				local enabled = racfg.enabled
				if enabled == nil then enabled = def.enabled == true end
				return enabled == true
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = string.format("%s %s", COMPACT_UNIT_FRAME_PROFILE_DISPLAYMAINTANKANDASSIST or "Main Tank & Assist", L["Anchor"] or "Anchor"),
			kind = SettingType.Dropdown,
			field = "raidAssignmentIconPoint",
			parentId = "groupicons",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				return racfg.point or def.point or "TOPRIGHT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidAssignmentIcon = cfg.status.raidAssignmentIcon or {}
				cfg.status.raidAssignmentIcon.point = value
				cfg.status.raidAssignmentIcon.relativePoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidAssignmentIconPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				local enabled = racfg.enabled
				if enabled == nil then enabled = def.enabled == true end
				return enabled == true
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = string.format("%s %s", COMPACT_UNIT_FRAME_PROFILE_DISPLAYMAINTANKANDASSIST or "Main Tank & Assist", L["Offset X"] or "Offset X"),
			kind = SettingType.Slider,
			allowInput = true,
			field = "raidAssignmentIconOffsetX",
			parentId = "groupicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				return racfg.x or def.x or -2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidAssignmentIcon = cfg.status.raidAssignmentIcon or {}
				cfg.status.raidAssignmentIcon.x = clampNumber(value, -200, 200, cfg.status.raidAssignmentIcon.x or -2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidAssignmentIconOffsetX", cfg.status.raidAssignmentIcon.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				local enabled = racfg.enabled
				if enabled == nil then enabled = def.enabled == true end
				return enabled == true
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = string.format("%s %s", COMPACT_UNIT_FRAME_PROFILE_DISPLAYMAINTANKANDASSIST or "Main Tank & Assist", L["Offset Y"] or "Offset Y"),
			kind = SettingType.Slider,
			allowInput = true,
			field = "raidAssignmentIconOffsetY",
			parentId = "groupicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				return racfg.y or def.y or -2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidAssignmentIcon = cfg.status.raidAssignmentIcon or {}
				cfg.status.raidAssignmentIcon.y = clampNumber(value, -200, 200, cfg.status.raidAssignmentIcon.y or -2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidAssignmentIconOffsetY", cfg.status.raidAssignmentIcon.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local racfg = sc.raidAssignmentIcon or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status.raidAssignmentIcon) or {}
				local enabled = racfg.enabled
				if enabled == nil then enabled = def.enabled == true end
				return enabled == true
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Show assist icon"] or "Show assist icon",
			kind = SettingType.Checkbox,
			field = "assistIconEnabled",
			parentId = "groupicons",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.assistIcon = cfg.status.assistIcon or {}
				cfg.status.assistIcon.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "assistIconEnabled", cfg.status.assistIcon.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Assist icon size"] or "Assist icon size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "assistIconSize",
			parentId = "groupicons",
			minValue = 8,
			maxValue = 40,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.size or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.assistIcon = cfg.status.assistIcon or {}
				cfg.status.assistIcon.size = clampNumber(value, 8, 40, cfg.status.assistIcon.size or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "assistIconSize", cfg.status.assistIcon.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.enabled ~= false
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Assist icon anchor"] or "Assist icon anchor",
			kind = SettingType.Dropdown,
			field = "assistIconPoint",
			parentId = "groupicons",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.point or "TOPLEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.assistIcon = cfg.status.assistIcon or {}
				cfg.status.assistIcon.point = value
				cfg.status.assistIcon.relativePoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "assistIconPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.enabled ~= false
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Assist icon offset X"] or "Assist icon offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "assistIconOffsetX",
			parentId = "groupicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.assistIcon = cfg.status.assistIcon or {}
				cfg.status.assistIcon.x = clampNumber(value, -200, 200, cfg.status.assistIcon.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "assistIconOffsetX", cfg.status.assistIcon.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.enabled ~= false
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Assist icon offset Y"] or "Assist icon offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "assistIconOffsetY",
			parentId = "groupicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.assistIcon = cfg.status.assistIcon or {}
				cfg.status.assistIcon.y = clampNumber(value, -200, 200, cfg.status.assistIcon.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "assistIconOffsetY", cfg.status.assistIcon.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local acfg = sc.assistIcon or {}
				return acfg.enabled ~= false
			end,
			isShown = function() return kind == "raid" end,
		},
		{
			name = L["Raid marker"] or "Raid marker",
			kind = SettingType.Collapsible,
			id = "raidmarker",
			defaultCollapsed = true,
		},
		{
			name = L["Show raid marker"] or "Show raid marker",
			kind = SettingType.Checkbox,
			field = "raidIconEnabled",
			parentId = "raidmarker",
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidIcon = cfg.status.raidIcon or {}
				cfg.status.raidIcon.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidIconEnabled", cfg.status.raidIcon.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRaidIcons()
			end,
		},
		{
			name = L["Raid marker size"] or "Raid marker size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "raidIconSize",
			parentId = "raidmarker",
			minValue = 8,
			maxValue = 40,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.size or 18
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidIcon = cfg.status.raidIcon or {}
				cfg.status.raidIcon.size = clampNumber(value, 8, 40, cfg.status.raidIcon.size or 18)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidIconSize", cfg.status.raidIcon.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRaidIcons()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Raid marker anchor"] or "Raid marker anchor",
			kind = SettingType.Dropdown,
			field = "raidIconPoint",
			parentId = "raidmarker",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.point or "TOP"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidIcon = cfg.status.raidIcon or {}
				cfg.status.raidIcon.point = value
				cfg.status.raidIcon.relativePoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidIconPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRaidIcons()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Raid marker offset X"] or "Raid marker offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "raidIconOffsetX",
			parentId = "raidmarker",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidIcon = cfg.status.raidIcon or {}
				cfg.status.raidIcon.x = clampNumber(value, -200, 200, cfg.status.raidIcon.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidIconOffsetX", cfg.status.raidIcon.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRaidIcons()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Raid marker offset Y"] or "Raid marker offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "raidIconOffsetY",
			parentId = "raidmarker",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.status = cfg.status or {}
				cfg.status.raidIcon = cfg.status.raidIcon or {}
				cfg.status.raidIcon.y = clampNumber(value, -200, 200, cfg.status.raidIcon.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "raidIconOffsetY", cfg.status.raidIcon.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				GF:RefreshRaidIcons()
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local sc = cfg and cfg.status or {}
				local rc = sc.raidIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Role icons"] or "Role icons",
			kind = SettingType.Collapsible,
			id = "roleicons",
			defaultCollapsed = true,
		},
		{
			name = L["Enable role icons"] or "Enable role icons",
			kind = SettingType.Checkbox,
			field = "roleIconEnabled",
			parentId = "roleicons",
			get = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.roleIcon = cfg.roleIcon or {}
				cfg.roleIcon.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconEnabled", cfg.roleIcon.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Role icon size"] or "Role icon size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "roleIconSize",
			parentId = "roleicons",
			minValue = 8,
			maxValue = 40,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.size or 14
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.roleIcon = cfg.roleIcon or {}
				cfg.roleIcon.size = clampNumber(value, 8, 40, cfg.roleIcon.size or 14)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconSize", cfg.roleIcon.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Role icon anchor"] or "Role icon anchor",
			kind = SettingType.Dropdown,
			field = "roleIconPoint",
			parentId = "roleicons",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.point or "LEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.roleIcon = cfg.roleIcon or {}
				cfg.roleIcon.point = value
				cfg.roleIcon.relativePoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Role icon offset X"] or "Role icon offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "roleIconOffsetX",
			parentId = "roleicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.roleIcon = cfg.roleIcon or {}
				cfg.roleIcon.x = clampNumber(value, -200, 200, cfg.roleIcon.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconOffsetX", cfg.roleIcon.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Role icon offset Y"] or "Role icon offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "roleIconOffsetY",
			parentId = "roleicons",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.roleIcon = cfg.roleIcon or {}
				cfg.roleIcon.y = clampNumber(value, -200, 200, cfg.roleIcon.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconOffsetY", cfg.roleIcon.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Role icon style"] or "Role icon style",
			kind = SettingType.Dropdown,
			field = "roleIconStyle",
			parentId = "roleicons",
			get = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.style or "TINY"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.roleIcon = cfg.roleIcon or {}
				cfg.roleIcon.style = value or "TINY"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconStyle", cfg.roleIcon.style, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				local tinyLabel = "|A:roleicon-tiny-tank:16:16|a |A:roleicon-tiny-healer:16:16|a |A:roleicon-tiny-dps:16:16|a"
				local circleLabel = "|A:UI-LFG-RoleIcon-Tank-Micro-GroupFinder:16:16|a |A:UI-LFG-RoleIcon-Healer-Micro-GroupFinder:16:16|a |A:UI-LFG-RoleIcon-DPS-Micro-GroupFinder:16:16|a"
				local frameLabel = "|A:UI-Frame-TankIcon:16:16|a |A:UI-Frame-HealerIcon:16:16|a |A:UI-Frame-DpsIcon:16:16|a"
				local options = {
					{ value = "TINY", label = tinyLabel },
					{ value = "CIRCLE", label = circleLabel },
					{ value = "FRAME", label = frameLabel },
				}
				for _, option in ipairs(options) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local rc = cfg and cfg.roleIcon or {}
						return (rc.style or "TINY") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.roleIcon = cfg.roleIcon or {}
						cfg.roleIcon.style = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconStyle", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Show role icons for roles"] or "Show role icons for roles",
			kind = SettingType.MultiDropdown,
			field = "roleIconRoles",
			height = 120,
			values = GF._sharedEdit.roleOpts,
			parentId = "roleicons",
			isSelected = function(_, value)
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				local selection = rc.showRoles
				if type(selection) ~= "table" then return true end
				return GFH.SelectionContains(selection, value)
			end,
			setSelected = function(_, value, state)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.roleIcon = cfg.roleIcon or {}
				local selection = cfg.roleIcon.showRoles
				if type(selection) ~= "table" then
					selection = GF._sharedEdit.defaultRoleSel()
					cfg.roleIcon.showRoles = selection
				end
				if state then
					selection[value] = true
				else
					selection[value] = nil
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "roleIconRoles", GF._sharedEdit.csm(selection), nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local rc = cfg and cfg.roleIcon or {}
				return rc.enabled ~= false
			end,
		},
		{
			name = L["Power"] or "Power",
			kind = SettingType.Collapsible,
			id = "power",
			defaultCollapsed = true,
		},
		{
			name = L["Show power for roles"] or "Show power for roles",
			kind = SettingType.MultiDropdown,
			field = "powerRoles",
			height = 140,
			values = GF._sharedEdit.roleOpts,
			parentId = "power",
			isSelected = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local selection = pcfg.showRoles
				if type(selection) ~= "table" then return true end
				return GFH.SelectionContains(selection, value)
			end,
			setSelected = function(_, value, state)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				local selection = cfg.power.showRoles
				if type(selection) ~= "table" then
					selection = GF._sharedEdit.defaultRoleSel()
					cfg.power.showRoles = selection
				end
				if state then
					selection[value] = true
				else
					selection[value] = nil
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerRoles", GF._sharedEdit.csm(selection), nil, true) end
				GF:RefreshPowerVisibility()
			end,
		},
		{
			name = L["Show power for specs"] or "Show power for specs",
			kind = SettingType.MultiDropdown,
			field = "powerSpecs",
			height = 240,
			values = specOptions,
			parentId = "power",
			isSelected = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local selection = pcfg.showSpecs
				if value == "__ALL__" then
					if type(selection) ~= "table" then return true end
					for _, opt in ipairs(specOptions) do
						if opt.value ~= "__ALL__" and not GFH.SelectionContains(selection, opt.value) then return false end
					end
					return true
				end
				if type(selection) ~= "table" then return true end
				return GFH.SelectionContains(selection, value)
			end,
			setSelected = function(_, value, state)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				local selection = cfg.power.showSpecs
				if type(selection) ~= "table" then
					selection = GF._sharedEdit.defaultSpecSel()
					cfg.power.showSpecs = selection
				end
				if value == "__ALL__" then
					for _, opt in ipairs(specOptions) do
						if opt.value ~= "__ALL__" then
							if state then
								selection[opt.value] = true
							else
								selection[opt.value] = nil
							end
						end
					end
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerSpecs", GF._sharedEdit.csm(selection), nil, true) end
					GF:RefreshPowerVisibility()
					return
				end
				if state then
					selection[value] = true
				else
					selection[value] = nil
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerSpecs", GF._sharedEdit.csm(selection), nil, true) end
				GF:RefreshPowerVisibility()
			end,
		},
		{
			name = L["UFPowerDetached"] or "Detach power bar",
			kind = SettingType.Checkbox,
			field = "powerDetached",
			parentId = "power",
			isShown = function() return kind == "party" end,
			get = function()
				local cfg = getCfg(kind)
				return GF.IsGroupPowerDetached(cfg, kind)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detached = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetached", cfg.power.detached, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
		},
		{
			name = L["UFPowerDetachedMatchHealthWidth"] or "Match health width",
			kind = SettingType.Checkbox,
			field = "powerDetachedMatchHealthWidth",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
				local value = pcfg.detachedMatchHealthWidth
				if value == nil then value = def.detachedMatchHealthWidth end
				if value == nil then value = true end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedMatchHealthWidth = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedMatchHealthWidth", cfg.power.detachedMatchHealthWidth, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = L["UFPowerWidth"] or "Power width",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerDetachedWidth",
			parentId = "power",
			minValue = 10,
			maxValue = 600,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.width or cfg.width or (DEFAULTS[kind] and DEFAULTS[kind].width) or 100
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.width = clampNumber(value, 10, 600, cfg.power.width or cfg.width or 100)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedWidth", cfg.power.width, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDetachedPowerWidthUnlocked,
			isShown = isPowerDetached,
		},
		{
			name = L["Power height"] or "Power height",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerDetachedHeight",
			parentId = "power",
			minValue = 1,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.detachedHeight or cfg.powerHeight or (DEFAULTS[kind] and DEFAULTS[kind].powerHeight) or 6
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedHeight = clampNumber(value, 1, 1000, cfg.power.detachedHeight or cfg.powerHeight or 6)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedHeight", cfg.power.detachedHeight, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = L["Show border"] or "Show border",
			kind = SettingType.Checkbox,
			field = "powerDetachedBorder",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.detachedBorder == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedBorder = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedBorder", cfg.power.detachedBorder, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = L["Border texture"] or "Border texture",
			kind = SettingType.Dropdown,
			field = "powerDetachedBorderTexture",
			parentId = "power",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local bc = cfg and cfg.border or {}
				return pcfg.detachedBorderTexture or bc.texture or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.texture) or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedBorderTexture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedBorderTexture", cfg.power.detachedBorderTexture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(borderOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						local bc = cfg and cfg.border or {}
						return (pcfg.detachedBorderTexture or bc.texture or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.texture) or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.detachedBorderTexture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedBorderTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = isDetachedPowerBorderEnabled,
			isShown = isPowerDetached,
		},
		{
			name = L["Border size"] or "Border size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerDetachedBorderSize",
			parentId = "power",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local bc = cfg and cfg.border or {}
				local bdef = (DEFAULTS[kind] and DEFAULTS[kind].border) or {}
				return pcfg.detachedBorderSize or bc.edgeSize or bdef.edgeSize or 1
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedBorderSize = clampNumber(value, 1, 64, cfg.power.detachedBorderSize or 1)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedBorderSize", cfg.power.detachedBorderSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDetachedPowerBorderEnabled,
			isShown = isPowerDetached,
		},
		{
			name = L["Border offset"] or "Border offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerDetachedBorderOffset",
			parentId = "power",
			minValue = 0,
			maxValue = 100,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local bc = cfg and cfg.border or {}
				local bdef = (DEFAULTS[kind] and DEFAULTS[kind].border) or {}
				local fallback = bc.offset
				if fallback == nil then fallback = bdef.offset end
				if fallback == nil then fallback = pcfg.detachedBorderSize or bc.edgeSize or bdef.edgeSize or 1 end
				return pcfg.detachedBorderOffset or fallback
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedBorderOffset = clampNumber(value, 0, 100, cfg.power.detachedBorderOffset or 1)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedBorderOffset", cfg.power.detachedBorderOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isDetachedPowerBorderEnabled,
			isShown = isPowerDetached,
		},
		{
			name = L["UFDetachedPowerStrata"] or "Strata",
			kind = SettingType.Dropdown,
			field = "powerDetachedStrata",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return GF.NormalizeFrameStrataToken(pcfg.detachedStrata) or ""
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedStrata = GF.NormalizeFrameStrataToken(value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedStrata", cfg.power.detachedStrata or "", nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			getValueText = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return GF.DropdownOptionLabel(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT, GF.NormalizeFrameStrataToken(pcfg.detachedStrata) or "", DEFAULT or "Default")
			end,
			generator = GF.DropdownRadioGenerator(GF._FRAME_STRATA_OPTIONS_WITH_DEFAULT),
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = L["UFDetachedPowerLevelOffset"] or "Frame level offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerDetachedFrameLevelOffset",
			parentId = "power",
			minValue = -20,
			maxValue = 1000,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local value = pcfg.detachedFrameLevelOffset
				if value == nil then value = (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.detachedFrameLevelOffset) end
				value = clampNumber(value, -20, 1000, 2)
				return floor(value + (value >= 0 and 0.5 or -0.5))
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				local levelOffset = clampNumber(value, -20, 1000, cfg.power.detachedFrameLevelOffset or 2)
				levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
				cfg.power.detachedFrameLevelOffset = levelOffset
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedFrameLevelOffset", cfg.power.detachedFrameLevelOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = "Grow from center",
			kind = SettingType.Checkbox,
			field = "powerDetachedGrowFromCenter",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
				local value = pcfg.detachedGrowFromCenter
				if value == nil then value = def.detachedGrowFromCenter end
				return value == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.detachedGrowFromCenter = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedGrowFromCenter", cfg.power.detachedGrowFromCenter, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = L["Offset X"] or "Offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerDetachedOffsetX",
			parentId = "power",
			minValue = -400,
			maxValue = 400,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offset and pcfg.offset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offset = cfg.power.offset or {}
				cfg.power.offset.x = clampNumber(value, -400, 400, cfg.power.offset.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedOffsetX", cfg.power.offset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = L["Offset Y"] or "Offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerDetachedOffsetY",
			parentId = "power",
			minValue = -400,
			maxValue = 400,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offset and pcfg.offset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offset = cfg.power.offset or {}
				cfg.power.offset.y = clampNumber(value, -400, 400, cfg.power.offset.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDetachedOffsetY", cfg.power.offset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPowerDetached,
			isShown = isPowerDetached,
		},
		{
			name = L["Left text"] or "Left text",
			kind = SettingType.Dropdown,
			field = "powerTextLeft",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.textLeft or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textLeft) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.textLeft = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTextLeft", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.textModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						return (pcfg.textLeft or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textLeft) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.textLeft = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTextLeft", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Center text"] or "Center text",
			kind = SettingType.Dropdown,
			field = "powerTextCenter",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.textCenter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textCenter) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.textCenter = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTextCenter", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.textModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						return (pcfg.textCenter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textCenter) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.textCenter = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTextCenter", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Right text"] or "Right text",
			kind = SettingType.Dropdown,
			field = "powerTextRight",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.textRight or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textRight) or "NONE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.textRight = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTextRight", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.textModeOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						return (pcfg.textRight or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textRight) or "NONE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.textRight = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTextRight", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Delimiter"] or "Delimiter",
			kind = SettingType.Dropdown,
			field = "powerDelimiter",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiter) or " "
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.textDelimiter = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDelimiter", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.delimiterOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						return (pcfg.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiter) or " ") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.textDelimiter = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDelimiter", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isShown = function() return powerDelimiterCount() >= 1 end,
		},
		{
			name = L["Secondary Delimiter"] or "Secondary delimiter",
			kind = SettingType.Dropdown,
			field = "powerDelimiterSecondary",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local primary = pcfg.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiter) or " "
				return pcfg.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterSecondary) or primary
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.textDelimiterSecondary = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDelimiterSecondary", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.delimiterOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						local primary = pcfg.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiter) or " "
						return (pcfg.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterSecondary) or primary) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.textDelimiterSecondary = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDelimiterSecondary", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isShown = function() return powerDelimiterCount() >= 2 end,
		},
		{
			name = L["Tertiary Delimiter"] or "Tertiary delimiter",
			kind = SettingType.Dropdown,
			field = "powerDelimiterTertiary",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local primary = pcfg.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiter) or " "
				local secondary = pcfg.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterSecondary) or primary
				return pcfg.textDelimiterTertiary or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterTertiary) or secondary
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.textDelimiterTertiary = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDelimiterTertiary", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.delimiterOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						local primary = pcfg.textDelimiter or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiter) or " "
						local secondary = pcfg.textDelimiterSecondary or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterSecondary) or primary
						return (pcfg.textDelimiterTertiary or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterTertiary) or secondary) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.textDelimiterTertiary = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerDelimiterTertiary", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isShown = function() return powerDelimiterCount() >= 3 end,
		},
		{
			name = L["Use short numbers"] or "Use short numbers",
			kind = SettingType.Checkbox,
			field = "powerShortNumbers",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				if pcfg.useShortNumbers == nil then return (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.useShortNumbers) ~= false end
				return pcfg.useShortNumbers ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.useShortNumbers = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerShortNumbers", cfg.power.useShortNumbers, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Round percent values"] or "Round percent values",
			kind = SettingType.Checkbox,
			field = "powerRoundPercent",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.roundPercent == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.roundPercent = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerRoundPercent", cfg.power.roundPercent, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Hide % symbol"] or "Hide % symbol",
			kind = SettingType.Checkbox,
			field = "powerHidePercent",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.hidePercentSymbol == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.hidePercentSymbol = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerHidePercent", cfg.power.hidePercentSymbol, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = FONT_SIZE,
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerFontSize",
			parentId = "power",
			minValue = 8,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.fontSize or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.fontSize) or 10
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.fontSize = clampNumber(value, 8, 30, cfg.power.fontSize or 10)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerFontSize", cfg.power.fontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Font"] or "Font",
			kind = SettingType.Dropdown,
			field = "powerFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.font or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.font) or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.font = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerFont", cfg.power.font, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						return (pcfg.font or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.font) or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.font = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["Font outline"] or "Font outline",
			kind = SettingType.Dropdown,
			field = "powerFontOutline",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return normalizeFontStyleChoice(pcfg.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.fontOutline) or "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.fontOutline = normalizeFontStyleChoice(value, (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.fontOutline) or "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerFontOutline", cfg.power.fontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						return normalizeFontStyleChoice(pcfg.fontOutline, (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.fontOutline) or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.fontOutline = normalizeFontStyleChoice(option.value, (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.fontOutline) or "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerFontOutline", cfg.power.fontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
		},
		{
			name = L["TextLeftOffsetX"] or "Left text X offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerLeftX",
			parentId = "power",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offsetLeft and pcfg.offsetLeft.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offsetLeft = cfg.power.offsetLeft or {}
				cfg.power.offsetLeft.x = clampNumber(value, -200, 200, (cfg.power.offsetLeft and cfg.power.offsetLeft.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerLeftX", cfg.power.offsetLeft.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPowerTextEnabled("textLeft") end,
		},
		{
			name = L["TextLeftOffsetY"] or "Left text Y offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerLeftY",
			parentId = "power",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offsetLeft and pcfg.offsetLeft.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offsetLeft = cfg.power.offsetLeft or {}
				cfg.power.offsetLeft.y = clampNumber(value, -200, 200, (cfg.power.offsetLeft and cfg.power.offsetLeft.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerLeftY", cfg.power.offsetLeft.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPowerTextEnabled("textLeft") end,
		},
		{
			name = L["TextCenterOffsetX"] or "Center text X offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerCenterX",
			parentId = "power",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offsetCenter and pcfg.offsetCenter.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offsetCenter = cfg.power.offsetCenter or {}
				cfg.power.offsetCenter.x = clampNumber(value, -200, 200, (cfg.power.offsetCenter and cfg.power.offsetCenter.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerCenterX", cfg.power.offsetCenter.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPowerTextEnabled("textCenter") end,
		},
		{
			name = L["TextCenterOffsetY"] or "Center text Y offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerCenterY",
			parentId = "power",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offsetCenter and pcfg.offsetCenter.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offsetCenter = cfg.power.offsetCenter or {}
				cfg.power.offsetCenter.y = clampNumber(value, -200, 200, (cfg.power.offsetCenter and cfg.power.offsetCenter.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerCenterY", cfg.power.offsetCenter.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPowerTextEnabled("textCenter") end,
		},
		{
			name = L["TextRightOffsetX"] or "Right text X offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerRightX",
			parentId = "power",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offsetRight and pcfg.offsetRight.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offsetRight = cfg.power.offsetRight or {}
				cfg.power.offsetRight.x = clampNumber(value, -200, 200, (cfg.power.offsetRight and cfg.power.offsetRight.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerRightX", cfg.power.offsetRight.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPowerTextEnabled("textRight") end,
		},
		{
			name = L["TextRightOffsetY"] or "Right text Y offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "powerRightY",
			parentId = "power",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return (pcfg.offsetRight and pcfg.offsetRight.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.offsetRight = cfg.power.offsetRight or {}
				cfg.power.offsetRight.y = clampNumber(value, -200, 200, (cfg.power.offsetRight and cfg.power.offsetRight.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerRightY", cfg.power.offsetRight.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isPowerTextEnabled("textRight") end,
		},
		{
			name = L["Bar Texture"] or "Bar Texture",
			kind = SettingType.Dropdown,
			field = "powerTexture",
			parentId = "power",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.texture or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.texture) or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTexture", cfg.power.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						return (pcfg.texture or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.texture) or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				return usesPerBarTextureSelection(cfg)
			end,
		},
		{
			name = L["Smooth fill"] or "Smooth fill",
			kind = SettingType.Checkbox,
			field = "powerSmoothFill",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
				if pcfg.smoothFill == nil then return def.smoothFill == true end
				return pcfg.smoothFill == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.smoothFill = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerSmoothFill", cfg.power.smoothFill, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show bar backdrop"] or "Show bar backdrop",
			kind = SettingType.Checkbox,
			field = "powerBackdropEnabled",
			parentId = "power",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
				local defBackdrop = def and def.backdrop or {}
				if pcfg.backdrop and pcfg.backdrop.enabled ~= nil then return pcfg.backdrop.enabled ~= false end
				return defBackdrop.enabled ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.backdrop = cfg.power.backdrop or {}
				cfg.power.backdrop.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerBackdropEnabled", cfg.power.backdrop.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Backdrop texture"] or "Backdrop texture",
			kind = SettingType.Dropdown,
			field = "powerBackdropTexture",
			parentId = "power",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
				local defBackdrop = def and def.backdrop or {}
				return (pcfg.backdrop and pcfg.backdrop.texture) or defBackdrop.texture or "DEFAULT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.power = cfg.power or {}
				cfg.power.backdrop = cfg.power.backdrop or {}
				cfg.power.backdrop.texture = value or "DEFAULT"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerBackdropTexture", cfg.power.backdrop.texture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(textureOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local pcfg = cfg and cfg.power or {}
						local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
						local defBackdrop = def and def.backdrop or {}
						return ((pcfg.backdrop and pcfg.backdrop.texture) or defBackdrop.texture or "DEFAULT") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.power = cfg.power or {}
						cfg.power.backdrop = cfg.power.backdrop or {}
						cfg.power.backdrop.texture = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerBackdropTexture", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local def = DEFAULTS[kind] and DEFAULTS[kind].power or {}
				local defBackdrop = def and def.backdrop or {}
				local enabled = pcfg.backdrop and pcfg.backdrop.enabled
				if enabled == nil then enabled = defBackdrop.enabled end
				if enabled == nil then enabled = true end
				return enabled ~= false
			end,
		},
		{
			name = L["Backdrop color"] or "Backdrop color",
			kind = SettingType.Color,
			field = "powerBackdropColor",
			parentId = "power",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.backdrop and DEFAULTS[kind].power.backdrop.color) or { 0, 0, 0, 0.6 },
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				local def = (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.backdrop and DEFAULTS[kind].power.backdrop.color) or { 0, 0, 0, 0.6 }
				local r, g, b, a = unpackColor(pcfg.backdrop and pcfg.backdrop.color, def)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.power = cfg.power or {}
				cfg.power.backdrop = cfg.power.backdrop or {}
				cfg.power.backdrop.color = { value.r or 0, value.g or 0, value.b or 0, value.a or 0.6 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "powerBackdropColor", cfg.power.backdrop.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.power or {}
				return pcfg.backdrop and pcfg.backdrop.enabled ~= false
			end,
		},
		{
			name = L["Buffs"] or "Buffs",
			kind = SettingType.Collapsible,
			id = "buffs",
			defaultCollapsed = true,
		},
		{
			name = L["Enable buffs"] or "Enable buffs",
			kind = SettingType.Checkbox,
			field = "buffsEnabled",
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.enabled = value and true or false
				GFH.SyncAurasEnabled(cfg)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffsEnabled", ac.buff.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["UFGroupBuffFilter"] or "Buff filter",
			kind = SettingType.MultiDropdown,
			field = "buffFilters",
			height = 80,
			parentId = "buffs",
			values = GF.groupBuffFilterOptions,
			isSelected = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return GF.IsGroupBuffFilterSelected(ac.buff, value)
			end,
			setSelected = function(_, value, state)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local selection = GF.SetGroupBuffFilterSelected(ac.buff, value, state)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffFilters", GF._sharedEdit.csm(selection), nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" or kind == "raid" end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.enabled == true
			end,
		},
		{
			name = L["Buff anchor"] or "Buff anchor",
			kind = SettingType.Dropdown,
			field = "buffAnchor",
			parentId = "buffs",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.anchorPoint or "TOPLEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.anchorPoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Anchor outside frame"] or "Anchor outside frame",
			kind = SettingType.Checkbox,
			field = "buffAnchorOutside",
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.anchorOutside == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.anchorOutside = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffAnchorOutside", ac.buff.anchorOutside, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" end,
		},
		{
			name = L["Buff growth direction"] or "Buff growth direction",
			kind = SettingType.Dropdown,
			field = "buffGrowth",
			parentId = "buffs",
			generator = auraGrowthGenerator(),
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return getAuraGrowthValue(ac.buff, ac.buff.anchorPoint)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				applyAuraGrowth(ac.buff, value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffGrowth", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Buff offset X"] or "Buff offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffOffsetX",
			parentId = "buffs",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.x = clampNumber(value, -200, 200, ac.buff.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffOffsetX", ac.buff.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Buff offset Y"] or "Buff offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffOffsetY",
			parentId = "buffs",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.y = clampNumber(value, -200, 200, ac.buff.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffOffsetY", ac.buff.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Buff size"] or "Buff size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffSize",
			parentId = "buffs",
			minValue = 8,
			maxValue = 120,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.size or 16
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.size = clampNumber(value, 8, 120, ac.buff.size or 16)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffSize", ac.buff.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		auraIconShapeSetting("buff", "buffIconShape", "buffs"),
		{
			name = L["Buff per row"] or "Buff per row",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffPerRow",
			parentId = "buffs",
			minValue = 1,
			maxValue = 12,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.perRow or 6
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.perRow = clampNumber(value, 1, 12, ac.buff.perRow or 6)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffPerRow", ac.buff.perRow, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Buff max"] or "Buff max",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffMax",
			parentId = "buffs",
			minValue = 1,
			maxValue = 20,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return GF.ClampAuraCount(ac.buff.max, 6, 20) or 6
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.max = GF.ClampAuraCount(value, ac.buff.max or 6, 20) or 6
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffMax", ac.buff.max, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Buff spacing"] or "Buff spacing",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffSpacing",
			parentId = "buffs",
			minValue = 0,
			maxValue = 10,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.spacing or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.buff.spacing = clampNumber(value, 0, 10, ac.buff.spacing or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffSpacing", ac.buff.spacing, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "buffs",
		},
		{
			name = L["Border texture"] or "Border texture",
			kind = SettingType.Dropdown,
			field = "buffBorderTexture",
			parentId = "buffs",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				local shape = GF.NormalizeAuraIconShape(ac.buff.iconShape, def.iconShape or "DEFAULT")
				return GF.NormalizeAuraBorderTexture(ac.buff.borderTexture, def.borderTexture or "DEFAULT", shape)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				local shape = GF.NormalizeAuraIconShape(ac.buff.iconShape, def.iconShape or "DEFAULT")
				ac.buff.borderTexture = GF.NormalizeAuraBorderTexture(value, def.borderTexture or "DEFAULT", shape)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffBorderTexture", ac.buff.borderTexture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			generator = function(_, root)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				local shape = GF.NormalizeAuraIconShape(ac.buff.iconShape, def.iconShape or "DEFAULT")
				for _, option in ipairs(borderOptions(shape)) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
						local currentShape = GF.NormalizeAuraIconShape(ac.buff.iconShape, def.iconShape or "DEFAULT")
						return GF.NormalizeAuraBorderTexture(ac.buff.borderTexture, def.borderTexture or "DEFAULT", currentShape) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
						local currentShape = GF.NormalizeAuraIconShape(ac.buff.iconShape, def.iconShape or "DEFAULT")
						ac.buff.borderTexture = GF.NormalizeAuraBorderTexture(option.value, def.borderTexture or "DEFAULT", currentShape)
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffBorderTexture", ac.buff.borderTexture, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						requestEditModeSettingsRefresh()
					end)
				end
			end,
		},
		{
			name = L["Border size"] or "Border size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffBorderSize",
			parentId = "buffs",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				return ac.buff.borderSize or def.borderSize or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				ac.buff.borderSize = clampNumber(value, 1, 64, ac.buff.borderSize or def.borderSize or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffBorderSize", ac.buff.borderSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				return GF.IsAuraBorderSizeAdjustable(ac.buff, def)
			end,
		},
		{
			name = L["Border offset"] or "Border offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffBorderOffset",
			parentId = "buffs",
			minValue = -64,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				return ac.buff.borderOffset or def.borderOffset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				ac.buff.borderOffset = clampNumber(value, -64, 64, ac.buff.borderOffset or def.borderOffset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffBorderOffset", ac.buff.borderOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				return GF.IsAuraBorderAdjustable(ac.buff, def)
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "buffs",
		},
		{
			name = L["CooldownPanelShowCooldown"] or "Show cooldown",
			kind = SettingType.Checkbox,
			field = "buffCooldownEnabled",
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				if ac.buff.showCooldown == nil then return def.showCooldown ~= false end
				return ac.buff.showCooldown ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.showCooldown = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownEnabled", ac.buff.showCooldown, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show cooldown text"] or "Show cooldown text",
			kind = SettingType.Checkbox,
			field = "buffCooldownTextEnabled",
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				if ac.buff.showCooldownText == nil then return def.showCooldownText ~= false end
				return ac.buff.showCooldownText ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.showCooldownText = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextEnabled", ac.buff.showCooldownText, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Cooldown text anchor"] or "Cooldown text anchor",
			kind = SettingType.Dropdown,
			field = "buffCooldownTextAnchor",
			parentId = "buffs",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.cooldownAnchor or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.cooldownAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("buff") end,
		},
		{
			name = L["Cooldown text offset X"] or "Cooldown text offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffCooldownTextOffsetX",
			parentId = "buffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.buff.cooldownOffset and ac.buff.cooldownOffset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.cooldownOffset = ac.buff.cooldownOffset or {}
				ac.buff.cooldownOffset.x = clampNumber(value, -50, 50, (ac.buff.cooldownOffset and ac.buff.cooldownOffset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextOffsetX", ac.buff.cooldownOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("buff") end,
		},
		{
			name = L["Cooldown text offset Y"] or "Cooldown text offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffCooldownTextOffsetY",
			parentId = "buffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.buff.cooldownOffset and ac.buff.cooldownOffset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.cooldownOffset = ac.buff.cooldownOffset or {}
				ac.buff.cooldownOffset.y = clampNumber(value, -50, 50, (ac.buff.cooldownOffset and ac.buff.cooldownOffset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextOffsetY", ac.buff.cooldownOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("buff") end,
		},
		{
			name = L["Cooldown text size"] or "Cooldown text size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffCooldownTextSize",
			parentId = "buffs",
			minValue = 6,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.cooldownFontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.cooldownFontSize = clampNumber(value, 6, 30, ac.buff.cooldownFontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextSize", ac.buff.cooldownFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("buff") end,
		},
		{
			name = L["Cooldown text font"] or "Cooldown text font",
			kind = SettingType.Dropdown,
			field = "buffCooldownTextFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.cooldownFont or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.cooldownFont = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.buff.cooldownFont or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.buff.cooldownFont = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("buff") end,
		},
		{
			name = L["Cooldown text outline"] or "Cooldown text outline",
			kind = SettingType.Dropdown,
			field = "buffCooldownTextOutline",
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.cooldownFontOutline or "OUTLINE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.cooldownFontOutline = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextOutline", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.buff.cooldownFontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.buff.cooldownFontOutline = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffCooldownTextOutline", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("buff") end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "buffs",
		},
		{
			name = L["Show stacks"] or "Show stacks",
			kind = SettingType.Checkbox,
			field = "buffStackTextEnabled",
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.buff) or {}
				if ac.buff.showStacks == nil then return def.showStacks ~= false end
				return ac.buff.showStacks ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.showStacks = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackTextEnabled", ac.buff.showStacks, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Stack anchor"] or "Stack anchor",
			kind = SettingType.Dropdown,
			field = "buffStackAnchor",
			parentId = "buffs",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.countAnchor or "BOTTOMRIGHT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.countAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("buff") end,
		},
		{
			name = L["Stack offset X"] or "Stack offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffStackOffsetX",
			parentId = "buffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.buff.countOffset and ac.buff.countOffset.x) or -2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.countOffset = ac.buff.countOffset or {}
				ac.buff.countOffset.x = clampNumber(value, -50, 50, (ac.buff.countOffset and ac.buff.countOffset.x) or -2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackOffsetX", ac.buff.countOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("buff") end,
		},
		{
			name = L["Stack offset Y"] or "Stack offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffStackOffsetY",
			parentId = "buffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.buff.countOffset and ac.buff.countOffset.y) or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.countOffset = ac.buff.countOffset or {}
				ac.buff.countOffset.y = clampNumber(value, -50, 50, (ac.buff.countOffset and ac.buff.countOffset.y) or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackOffsetY", ac.buff.countOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("buff") end,
		},
		{
			name = L["Stack size"] or "Stack size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "buffStackSize",
			parentId = "buffs",
			minValue = 6,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.countFontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.countFontSize = clampNumber(value, 6, 30, ac.buff.countFontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackSize", ac.buff.countFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("buff") end,
		},
		{
			name = L["Stack font"] or "Stack font",
			kind = SettingType.Dropdown,
			field = "buffStackFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.countFont or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.countFont = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.buff.countFont or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.buff.countFont = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraStackTextEnabled("buff") end,
		},
		{
			name = L["Stack outline"] or "Stack outline",
			kind = SettingType.Dropdown,
			field = "buffStackOutline",
			parentId = "buffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.buff.countFontOutline or "OUTLINE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.buff.countFontOutline = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackOutline", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.buff.countFontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.buff.countFontOutline = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "buffStackOutline", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraStackTextEnabled("buff") end,
		},
		{
			name = L["Debuffs"] or "Debuffs",
			kind = SettingType.Collapsible,
			id = "debuffs",
			defaultCollapsed = true,
		},
		{
			name = L["Enable debuffs"] or "Enable debuffs",
			kind = SettingType.Checkbox,
			field = "debuffsEnabled",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.enabled = value and true or false
				GFH.SyncAurasEnabled(cfg)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffsEnabled", ac.debuff.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["UFGroupDebuffFilter"] or "Debuff filters",
			kind = SettingType.MultiDropdown,
			field = "debuffFilters",
			height = 140,
			values = groupDebuffFilterOptions,
			parentId = "debuffs",
			isSelected = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return isGroupDebuffFilterSelected(ac.debuff, value)
			end,
			setSelected = function(_, value, state)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local selection = setGroupDebuffFilterSelected(ac.debuff, value, state)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffFilters", GF._sharedEdit.csm(selection), nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" or kind == "raid" end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.enabled == true
			end,
		},
		{
			name = L["Debuff anchor"] or "Debuff anchor",
			kind = SettingType.Dropdown,
			field = "debuffAnchor",
			parentId = "debuffs",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.anchorPoint or "BOTTOMLEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.anchorPoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Anchor outside frame"] or "Anchor outside frame",
			kind = SettingType.Checkbox,
			field = "debuffAnchorOutside",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.anchorOutside == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.anchorOutside = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffAnchorOutside", ac.debuff.anchorOutside, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" end,
		},
		{
			name = L["Debuff growth direction"] or "Debuff growth direction",
			kind = SettingType.Dropdown,
			field = "debuffGrowth",
			parentId = "debuffs",
			generator = auraGrowthGenerator(),
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return getAuraGrowthValue(ac.debuff, ac.debuff.anchorPoint)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				applyAuraGrowth(ac.debuff, value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffGrowth", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Debuff offset X"] or "Debuff offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffOffsetX",
			parentId = "debuffs",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.x = clampNumber(value, -200, 200, ac.debuff.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffOffsetX", ac.debuff.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Debuff offset Y"] or "Debuff offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffOffsetY",
			parentId = "debuffs",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.y = clampNumber(value, -200, 200, ac.debuff.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffOffsetY", ac.debuff.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Debuff size"] or "Debuff size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffSize",
			parentId = "debuffs",
			minValue = 8,
			maxValue = 120,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.size or 16
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.size = clampNumber(value, 8, 120, ac.debuff.size or 16)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffSize", ac.debuff.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		auraIconShapeSetting("debuff", "debuffIconShape", "debuffs"),
		{
			name = L["Debuff per row"] or "Debuff per row",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffPerRow",
			parentId = "debuffs",
			minValue = 1,
			maxValue = 12,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.perRow or 6
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.perRow = clampNumber(value, 1, 12, ac.debuff.perRow or 6)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffPerRow", ac.debuff.perRow, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Debuff max"] or "Debuff max",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffMax",
			parentId = "debuffs",
			minValue = 1,
			maxValue = 20,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return GF.ClampAuraCount(ac.debuff.max, 6, 20) or 6
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.max = GF.ClampAuraCount(value, ac.debuff.max or 6, 20) or 6
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffMax", ac.debuff.max, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Display larger role-specific debuffs"] or "Display larger role-specific debuffs",
			kind = SettingType.Checkbox,
			field = "debuffDisplayLargerRoleSpecific",
			parentId = "debuffs",
			isShown = function() return GF.IsBlizzardAuraRenderTypeEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY, "debuffs") end,
			get = function() return GF.IsBlizzardLargerRoleDebuffEnabled(getCfg(kind), DEFAULTS[kind] or EMPTY) end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.displayLargerRoleSpecificDebuffs = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffDisplayLargerRoleSpecific", ac.debuff.displayLargerRoleSpecificDebuffs, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				refreshAllAuras()
				refreshAllPrivateAuras()
			end,
		},
		{
			name = L["Debuff spacing"] or "Debuff spacing",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffSpacing",
			parentId = "debuffs",
			minValue = 0,
			maxValue = 10,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.spacing or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.spacing = clampNumber(value, 0, 10, ac.debuff.spacing or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffSpacing", ac.debuff.spacing, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "debuffs",
		},
		{
			name = L["Border texture"] or "Border texture",
			kind = SettingType.Dropdown,
			field = "debuffBorderTexture",
			parentId = "debuffs",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				local shape = GF.NormalizeAuraIconShape(ac.debuff.iconShape, def.iconShape or "DEFAULT")
				return GF.NormalizeAuraBorderTexture(ac.debuff.borderTexture, def.borderTexture or "DEFAULT", shape)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				local shape = GF.NormalizeAuraIconShape(ac.debuff.iconShape, def.iconShape or "DEFAULT")
				ac.debuff.borderTexture = GF.NormalizeAuraBorderTexture(value, def.borderTexture or "DEFAULT", shape)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffBorderTexture", ac.debuff.borderTexture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			generator = function(_, root)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				local shape = GF.NormalizeAuraIconShape(ac.debuff.iconShape, def.iconShape or "DEFAULT")
				for _, option in ipairs(borderOptions(shape)) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
						local currentShape = GF.NormalizeAuraIconShape(ac.debuff.iconShape, def.iconShape or "DEFAULT")
						return GF.NormalizeAuraBorderTexture(ac.debuff.borderTexture, def.borderTexture or "DEFAULT", currentShape) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
						local currentShape = GF.NormalizeAuraIconShape(ac.debuff.iconShape, def.iconShape or "DEFAULT")
						ac.debuff.borderTexture = GF.NormalizeAuraBorderTexture(option.value, def.borderTexture or "DEFAULT", currentShape)
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffBorderTexture", ac.debuff.borderTexture, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						requestEditModeSettingsRefresh()
					end)
				end
			end,
		},
		{
			name = L["Border size"] or "Border size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffBorderSize",
			parentId = "debuffs",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				return ac.debuff.borderSize or def.borderSize or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				ac.debuff.borderSize = clampNumber(value, 1, 64, ac.debuff.borderSize or def.borderSize or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffBorderSize", ac.debuff.borderSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				return GF.IsAuraBorderSizeAdjustable(ac.debuff, def)
			end,
		},
		{
			name = L["Border offset"] or "Border offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffBorderOffset",
			parentId = "debuffs",
			minValue = -64,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				return ac.debuff.borderOffset or def.borderOffset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				ac.debuff.borderOffset = clampNumber(value, -64, 64, ac.debuff.borderOffset or def.borderOffset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffBorderOffset", ac.debuff.borderOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				return GF.IsAuraBorderAdjustable(ac.debuff, def)
			end,
		},
		{
			name = L["Show dispel icon"] or "Show dispel icon",
			kind = SettingType.Checkbox,
			field = "debuffShowDispelIcon",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				if ac.debuff.showDispelIcon == nil then return def.showDispelIcon ~= false end
				return ac.debuff.showDispelIcon ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.debuff.showDispelIcon = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffShowDispelIcon", ac.debuff.showDispelIcon, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "debuffs",
		},
		{
			name = L["CooldownPanelShowCooldown"] or "Show cooldown",
			kind = SettingType.Checkbox,
			field = "debuffCooldownEnabled",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				if ac.debuff.showCooldown == nil then return def.showCooldown ~= false end
				return ac.debuff.showCooldown ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.showCooldown = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownEnabled", ac.debuff.showCooldown, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show cooldown text"] or "Show cooldown text",
			kind = SettingType.Checkbox,
			field = "debuffCooldownTextEnabled",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				if ac.debuff.showCooldownText == nil then return def.showCooldownText ~= false end
				return ac.debuff.showCooldownText ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.showCooldownText = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextEnabled", ac.debuff.showCooldownText, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Cooldown text anchor"] or "Cooldown text anchor",
			kind = SettingType.Dropdown,
			field = "debuffCooldownTextAnchor",
			parentId = "debuffs",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.cooldownAnchor or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.cooldownAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("debuff") end,
		},
		{
			name = L["Cooldown text offset X"] or "Cooldown text offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffCooldownTextOffsetX",
			parentId = "debuffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.debuff.cooldownOffset and ac.debuff.cooldownOffset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.cooldownOffset = ac.debuff.cooldownOffset or {}
				ac.debuff.cooldownOffset.x = clampNumber(value, -50, 50, (ac.debuff.cooldownOffset and ac.debuff.cooldownOffset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextOffsetX", ac.debuff.cooldownOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("debuff") end,
		},
		{
			name = L["Cooldown text offset Y"] or "Cooldown text offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffCooldownTextOffsetY",
			parentId = "debuffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.debuff.cooldownOffset and ac.debuff.cooldownOffset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.cooldownOffset = ac.debuff.cooldownOffset or {}
				ac.debuff.cooldownOffset.y = clampNumber(value, -50, 50, (ac.debuff.cooldownOffset and ac.debuff.cooldownOffset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextOffsetY", ac.debuff.cooldownOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("debuff") end,
		},
		{
			name = L["Cooldown text size"] or "Cooldown text size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffCooldownTextSize",
			parentId = "debuffs",
			minValue = 6,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.cooldownFontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.cooldownFontSize = clampNumber(value, 6, 30, ac.debuff.cooldownFontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextSize", ac.debuff.cooldownFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("debuff") end,
		},
		{
			name = L["Cooldown text font"] or "Cooldown text font",
			kind = SettingType.Dropdown,
			field = "debuffCooldownTextFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.cooldownFont or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.cooldownFont = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.debuff.cooldownFont or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.debuff.cooldownFont = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("debuff") end,
		},
		{
			name = L["Cooldown text outline"] or "Cooldown text outline",
			kind = SettingType.Dropdown,
			field = "debuffCooldownTextOutline",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.cooldownFontOutline or "OUTLINE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.cooldownFontOutline = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextOutline", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.debuff.cooldownFontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.debuff.cooldownFontOutline = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffCooldownTextOutline", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("debuff") end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "debuffs",
		},
		{
			name = L["Show stacks"] or "Show stacks",
			kind = SettingType.Checkbox,
			field = "debuffStackTextEnabled",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.debuff) or {}
				if ac.debuff.showStacks == nil then return def.showStacks ~= false end
				return ac.debuff.showStacks ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.showStacks = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackTextEnabled", ac.debuff.showStacks, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Stack anchor"] or "Stack anchor",
			kind = SettingType.Dropdown,
			field = "debuffStackAnchor",
			parentId = "debuffs",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.countAnchor or "BOTTOMRIGHT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.countAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("debuff") end,
		},
		{
			name = L["Stack offset X"] or "Stack offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffStackOffsetX",
			parentId = "debuffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.debuff.countOffset and ac.debuff.countOffset.x) or -2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.countOffset = ac.debuff.countOffset or {}
				ac.debuff.countOffset.x = clampNumber(value, -50, 50, (ac.debuff.countOffset and ac.debuff.countOffset.x) or -2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackOffsetX", ac.debuff.countOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("debuff") end,
		},
		{
			name = L["Stack offset Y"] or "Stack offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffStackOffsetY",
			parentId = "debuffs",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.debuff.countOffset and ac.debuff.countOffset.y) or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.countOffset = ac.debuff.countOffset or {}
				ac.debuff.countOffset.y = clampNumber(value, -50, 50, (ac.debuff.countOffset and ac.debuff.countOffset.y) or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackOffsetY", ac.debuff.countOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("debuff") end,
		},
		{
			name = L["Stack size"] or "Stack size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "debuffStackSize",
			parentId = "debuffs",
			minValue = 6,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.countFontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.countFontSize = clampNumber(value, 6, 30, ac.debuff.countFontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackSize", ac.debuff.countFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("debuff") end,
		},
		{
			name = L["Stack font"] or "Stack font",
			kind = SettingType.Dropdown,
			field = "debuffStackFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.countFont or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.countFont = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.debuff.countFont or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.debuff.countFont = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraStackTextEnabled("debuff") end,
		},
		{
			name = L["Stack outline"] or "Stack outline",
			kind = SettingType.Dropdown,
			field = "debuffStackOutline",
			parentId = "debuffs",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.debuff.countFontOutline or "OUTLINE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.debuff.countFontOutline = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackOutline", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.debuff.countFontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.debuff.countFontOutline = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "debuffStackOutline", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraStackTextEnabled("debuff") end,
		},
		{
			name = L["Externals"] or "Externals",
			kind = SettingType.Collapsible,
			id = "externals",
			defaultCollapsed = true,
		},
		{
			name = L["Enable externals"] or "Enable externals",
			kind = SettingType.Checkbox,
			field = "externalsEnabled",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.enabled == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.enabled = value and true or false
				GFH.SyncAurasEnabled(cfg)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalsEnabled", ac.externals.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["External anchor"] or "External anchor",
			kind = SettingType.Dropdown,
			field = "externalAnchor",
			parentId = "externals",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.anchorPoint or "TOPRIGHT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.anchorPoint = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Anchor outside frame"] or "Anchor outside frame",
			kind = SettingType.Checkbox,
			field = "externalAnchorOutside",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.anchorOutside == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.anchorOutside = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalAnchorOutside", ac.externals.anchorOutside, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return kind == "party" end,
		},
		{
			name = L["External growth direction"] or "External growth direction",
			kind = SettingType.Dropdown,
			field = "externalGrowth",
			parentId = "externals",
			generator = auraGrowthGenerator(),
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return getAuraGrowthValue(ac.externals, ac.externals.anchorPoint)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				applyAuraGrowth(ac.externals, value)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalGrowth", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["External offset X"] or "External offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalOffsetX",
			parentId = "externals",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.x = clampNumber(value, -200, 200, ac.externals.x or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalOffsetX", ac.externals.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["External offset Y"] or "External offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalOffsetY",
			parentId = "externals",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.y = clampNumber(value, -200, 200, ac.externals.y or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalOffsetY", ac.externals.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["External size"] or "External size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalSize",
			parentId = "externals",
			minValue = 8,
			maxValue = 120,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.size or 16
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.size = clampNumber(value, 8, 120, ac.externals.size or 16)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalSize", ac.externals.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		auraIconShapeSetting("externals", "externalIconShape", "externals"),
		{
			name = L["External per row"] or "External per row",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalPerRow",
			parentId = "externals",
			minValue = 1,
			maxValue = 12,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.perRow or 6
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.perRow = clampNumber(value, 1, 12, ac.externals.perRow or 6)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalPerRow", ac.externals.perRow, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["External max"] or "External max",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalMax",
			parentId = "externals",
			minValue = 1,
			maxValue = 20,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return GF.ClampAuraCount(ac.externals.max, 4, 20) or 4
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.max = GF.ClampAuraCount(value, ac.externals.max or 4, 20) or 4
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalMax", ac.externals.max, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["External spacing"] or "External spacing",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalSpacing",
			parentId = "externals",
			minValue = 0,
			maxValue = 10,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.spacing or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.spacing = clampNumber(value, 0, 10, ac.externals.spacing or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalSpacing", ac.externals.spacing, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = _G.GLOW or "Glow",
			kind = SettingType.Checkbox,
			field = "externalGlowEnabled",
			parentId = "externals",
			default = false,
			get = function() return isExternalGlowShown() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.glowEnabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalGlowEnabled", ac.externals.glowEnabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
		},
		{
			name = _G.STYLE or "Style",
			kind = SettingType.Dropdown,
			field = "externalGlowStyle",
			parentId = "externals",
			default = "MARCHING_ANTS",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				return GF.NormalizeExternalGlowStyleForIconShape(ac.externals.glowStyle, def.glowStyle or "MARCHING_ANTS", ac.externals.iconShape or def.iconShape)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				ac.externals.glowStyle = GF.NormalizeExternalGlowStyleForIconShape(value, ac.externals.glowStyle or def.glowStyle or "MARCHING_ANTS", ac.externals.iconShape or def.iconShape)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalGlowStyle", ac.externals.glowStyle, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root, data)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				for _, option in ipairs(GF.GetExternalGlowStyleOptions(ac.externals.iconShape or def.iconShape)) do
					root:CreateRadio(option.label, function() return data.get and data.get() == option.value end, function()
						if data.set then data.set(nil, option.value) end
						data.customDefaultText = option.label
						requestEditModeSettingsRefresh()
					end)
				end
			end,
			isEnabled = isExternalGlowShown,
		},
		{
			name = _G.COLOR or "Color",
			kind = SettingType.Color,
			field = "externalGlowColor",
			parentId = "externals",
			hasOpacity = true,
			default = { 1, 0.25, 0.25, 1 },
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				local r, g, b, a = unpackColor(GF.GetExternalGlowColor(ac.externals, def), { 1, 0.25, 0.25, 1 })
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.glowColor = { value.r or 1, value.g or 0.25, value.b or 0.25, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalGlowColor", ac.externals.glowColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isExternalGlowShown,
		},
		{
			name = _G.OFFSET or "Offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalGlowInset",
			parentId = "externals",
			minValue = -20,
			maxValue = 20,
			valueStep = 1,
			default = 0,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				return GF.NormalizeExternalGlowInset(ac.externals.glowInset, def.glowInset or 0)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.glowInset = GF.NormalizeExternalGlowInset(value, ac.externals.glowInset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalGlowInset", ac.externals.glowInset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isExternalGlowShown,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "externals",
		},
		{
			name = L["Border texture"] or "Border texture",
			kind = SettingType.Dropdown,
			field = "externalBorderTexture",
			parentId = "externals",
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				local shape = GF.NormalizeAuraIconShape(ac.externals.iconShape, def.iconShape or "DEFAULT")
				return GF.NormalizeAuraBorderTexture(ac.externals.borderTexture, def.borderTexture or "DEFAULT", shape)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				local shape = GF.NormalizeAuraIconShape(ac.externals.iconShape, def.iconShape or "DEFAULT")
				ac.externals.borderTexture = GF.NormalizeAuraBorderTexture(value, def.borderTexture or "DEFAULT", shape)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalBorderTexture", ac.externals.borderTexture, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				requestEditModeSettingsRefresh()
			end,
			generator = function(_, root)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				local shape = GF.NormalizeAuraIconShape(ac.externals.iconShape, def.iconShape or "DEFAULT")
				for _, option in ipairs(borderOptions(shape)) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
						local currentShape = GF.NormalizeAuraIconShape(ac.externals.iconShape, def.iconShape or "DEFAULT")
						return GF.NormalizeAuraBorderTexture(ac.externals.borderTexture, def.borderTexture or "DEFAULT", currentShape) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
						local currentShape = GF.NormalizeAuraIconShape(ac.externals.iconShape, def.iconShape or "DEFAULT")
						ac.externals.borderTexture = GF.NormalizeAuraBorderTexture(option.value, def.borderTexture or "DEFAULT", currentShape)
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalBorderTexture", ac.externals.borderTexture, nil, true) end
						GF:ApplyHeaderAttributes(kind)
						requestEditModeSettingsRefresh()
					end)
				end
			end,
		},
		{
			name = L["Border size"] or "Border size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalBorderSize",
			parentId = "externals",
			minValue = 1,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				return ac.externals.borderSize or def.borderSize or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				ac.externals.borderSize = clampNumber(value, 1, 64, ac.externals.borderSize or def.borderSize or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalBorderSize", ac.externals.borderSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				return GF.IsAuraBorderSizeAdjustable(ac.externals, def)
			end,
		},
		{
			name = EMBLEM_BORDER_COLOR,
			kind = SettingType.Color,
			field = "externalBorderColor",
			parentId = "externals",
			hasOpacity = true,
			default = { 1, 0.25, 0.25, 1 },
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				local r, g, b, a = unpackColor(ac.externals.borderColor, def.borderColor or { 1, 0.25, 0.25, 1 })
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.borderColor = { value.r or 1, value.g or 0.25, value.b or 0.25, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalBorderColor", ac.externals.borderColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				return GF.IsAuraBorderAdjustable(ac.externals, def)
			end,
		},
		{
			name = L["Border offset"] or "Border offset",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalBorderOffset",
			parentId = "externals",
			minValue = -64,
			maxValue = 64,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				return ac.externals.borderOffset or def.borderOffset or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				ac.externals.borderOffset = clampNumber(value, -64, 64, ac.externals.borderOffset or def.borderOffset or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalBorderOffset", ac.externals.borderOffset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				return GF.IsAuraBorderAdjustable(ac.externals, def)
			end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "externals",
		},
		{
			name = L["CooldownPanelShowCooldown"] or "Show cooldown",
			kind = SettingType.Checkbox,
			field = "externalCooldownEnabled",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				if ac.externals.showCooldown == nil then return def.showCooldown ~= false end
				return ac.externals.showCooldown ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.showCooldown = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownEnabled", ac.externals.showCooldown, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Show cooldown text"] or "Show cooldown text",
			kind = SettingType.Checkbox,
			field = "externalCooldownTextEnabled",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				if ac.externals.showCooldownText == nil then return def.showCooldownText ~= false end
				return ac.externals.showCooldownText ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.showCooldownText = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextEnabled", ac.externals.showCooldownText, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Cooldown text anchor"] or "Cooldown text anchor",
			kind = SettingType.Dropdown,
			field = "externalCooldownTextAnchor",
			parentId = "externals",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.cooldownAnchor or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.cooldownAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("externals") end,
		},
		{
			name = L["Cooldown text offset X"] or "Cooldown text offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalCooldownTextOffsetX",
			parentId = "externals",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.externals.cooldownOffset and ac.externals.cooldownOffset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.cooldownOffset = ac.externals.cooldownOffset or {}
				ac.externals.cooldownOffset.x = clampNumber(value, -50, 50, (ac.externals.cooldownOffset and ac.externals.cooldownOffset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextOffsetX", ac.externals.cooldownOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("externals") end,
		},
		{
			name = L["Cooldown text offset Y"] or "Cooldown text offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalCooldownTextOffsetY",
			parentId = "externals",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.externals.cooldownOffset and ac.externals.cooldownOffset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.cooldownOffset = ac.externals.cooldownOffset or {}
				ac.externals.cooldownOffset.y = clampNumber(value, -50, 50, (ac.externals.cooldownOffset and ac.externals.cooldownOffset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextOffsetY", ac.externals.cooldownOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("externals") end,
		},
		{
			name = L["Cooldown text size"] or "Cooldown text size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalCooldownTextSize",
			parentId = "externals",
			minValue = 6,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.cooldownFontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.cooldownFontSize = clampNumber(value, 6, 30, ac.externals.cooldownFontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextSize", ac.externals.cooldownFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("externals") end,
		},
		{
			name = L["Cooldown text font"] or "Cooldown text font",
			kind = SettingType.Dropdown,
			field = "externalCooldownTextFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.cooldownFont or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.cooldownFont = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.externals.cooldownFont or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.externals.cooldownFont = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("externals") end,
		},
		{
			name = L["Cooldown text outline"] or "Cooldown text outline",
			kind = SettingType.Dropdown,
			field = "externalCooldownTextOutline",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.cooldownFontOutline or "OUTLINE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.cooldownFontOutline = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextOutline", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.externals.cooldownFontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.externals.cooldownFontOutline = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalCooldownTextOutline", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraCooldownTextEnabled("externals") end,
		},
		{
			name = "",
			kind = SettingType.Divider,
			parentId = "externals",
		},
		{
			name = L["Show stacks"] or "Show stacks",
			kind = SettingType.Checkbox,
			field = "externalStackTextEnabled",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local def = (DEFAULTS[kind] and DEFAULTS[kind].auras and DEFAULTS[kind].auras.externals) or {}
				if ac.externals.showStacks == nil then return def.showStacks ~= false end
				return ac.externals.showStacks ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.showStacks = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackTextEnabled", ac.externals.showStacks, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Stack anchor"] or "Stack anchor",
			kind = SettingType.Dropdown,
			field = "externalStackAnchor",
			parentId = "externals",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.countAnchor or "BOTTOMRIGHT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.countAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("externals") end,
		},
		{
			name = L["Stack offset X"] or "Stack offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalStackOffsetX",
			parentId = "externals",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.externals.countOffset and ac.externals.countOffset.x) or -2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.countOffset = ac.externals.countOffset or {}
				ac.externals.countOffset.x = clampNumber(value, -50, 50, (ac.externals.countOffset and ac.externals.countOffset.x) or -2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackOffsetX", ac.externals.countOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("externals") end,
		},
		{
			name = L["Stack offset Y"] or "Stack offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalStackOffsetY",
			parentId = "externals",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.externals.countOffset and ac.externals.countOffset.y) or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.countOffset = ac.externals.countOffset or {}
				ac.externals.countOffset.y = clampNumber(value, -50, 50, (ac.externals.countOffset and ac.externals.countOffset.y) or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackOffsetY", ac.externals.countOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("externals") end,
		},
		{
			name = L["Stack size"] or "Stack size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalStackSize",
			parentId = "externals",
			minValue = 6,
			maxValue = 30,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.countFontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.countFontSize = clampNumber(value, 6, 30, ac.externals.countFontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackSize", ac.externals.countFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isAuraStackTextEnabled("externals") end,
		},
		{
			name = L["Stack font"] or "Stack font",
			kind = SettingType.Dropdown,
			field = "externalStackFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.countFont or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.countFont = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.externals.countFont or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.externals.countFont = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraStackTextEnabled("externals") end,
		},
		{
			name = L["Stack outline"] or "Stack outline",
			kind = SettingType.Dropdown,
			field = "externalStackOutline",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.countFontOutline or "OUTLINE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local ac = ensureAuraConfig(cfg)
				ac.externals.countFontOutline = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackOutline", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.externals.countFontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local ac = ensureAuraConfig(cfg)
						ac.externals.countFontOutline = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalStackOutline", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isAuraStackTextEnabled("externals") end,
		},
		{
			name = L["Show DR %"] or "Show DR %",
			kind = SettingType.Checkbox,
			field = "externalDrEnabled",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.showDR == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.showDR = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrEnabled", ac.externals.showDR, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["DR anchor"] or "DR anchor",
			kind = SettingType.Dropdown,
			field = "externalDrAnchor",
			parentId = "externals",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.drAnchor or "TOPLEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.drAnchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isExternalDRShown,
		},
		{
			name = L["DR offset X"] or "DR offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalDrOffsetX",
			parentId = "externals",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.externals.drOffset and ac.externals.drOffset.x) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.drOffset = ac.externals.drOffset or {}
				ac.externals.drOffset.x = clampNumber(value, -50, 50, (ac.externals.drOffset and ac.externals.drOffset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrOffsetX", ac.externals.drOffset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isExternalDRShown,
		},
		{
			name = L["DR offset Y"] or "DR offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalDrOffsetY",
			parentId = "externals",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return (ac.externals.drOffset and ac.externals.drOffset.y) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.drOffset = ac.externals.drOffset or {}
				ac.externals.drOffset.y = clampNumber(value, -50, 50, (ac.externals.drOffset and ac.externals.drOffset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrOffsetY", ac.externals.drOffset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isExternalDRShown,
		},
		{
			name = L["DR color"] or "DR color",
			kind = SettingType.Color,
			field = "externalDrColor",
			parentId = "externals",
			hasOpacity = true,
			default = { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				local col = ac.externals.drColor or { 1, 1, 1, 1 }
				return { r = col[1] or 1, g = col[2] or 1, b = col[3] or 1, a = col[4] or 1 }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.drColor = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrColor", ac.externals.drColor, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isExternalDRShown,
		},
		{
			name = L["DR font size"] or "DR font size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "externalDrFontSize",
			parentId = "externals",
			minValue = 6,
			maxValue = 24,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.drFontSize or 10
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.drFontSize = clampNumber(value, 6, 24, ac.externals.drFontSize or 10)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrFontSize", ac.externals.drFontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isExternalDRShown,
		},
		{
			name = L["DR font"] or "DR font",
			kind = SettingType.Dropdown,
			field = "externalDrFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.drFont
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.drFont = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.externals.drFont or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						ac.externals.drFont = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = isExternalDRShown,
		},
		{
			name = L["DR font outline"] or "DR font outline",
			kind = SettingType.Dropdown,
			field = "externalDrFontOutline",
			parentId = "externals",
			get = function()
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				return ac.externals.drFontOutline or "OUTLINE"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local ac = ensureAuraConfig(cfg)
				ac.externals.drFontOutline = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrFontOutline", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						return (ac.externals.drFontOutline or "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						local ac = ensureAuraConfig(cfg)
						ac.externals.drFontOutline = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "externalDrFontOutline", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = isExternalDRShown,
		},
		{
			name = L["Private Auras"] or "Private Auras",
			kind = SettingType.Collapsible,
			id = "privateAuras",
			defaultCollapsed = true,
		},
		{
			name = L["Enable private auras"] or "Enable private auras",
			kind = SettingType.Checkbox,
			field = "privateAurasEnabled",
			parentId = "privateAuras",
			get = function() return isPrivateAurasEnabled() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasEnabled", pcfg.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		},
		{
			name = L["Amount"] or "Amount",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasAmount",
			parentId = "privateAuras",
			minValue = 1,
			maxValue = 10,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local icon = pcfg.icon or {}
				local defIcon = defPrivateAuras.icon or {}
				return icon.amount or defIcon.amount or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.icon.amount = clampNumber(value, 1, 10, pcfg.icon.amount or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasAmount", pcfg.icon.amount, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Icon size"] or "Icon size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasSize",
			parentId = "privateAuras",
			minValue = 8,
			maxValue = 120,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local icon = pcfg.icon or {}
				local defIcon = defPrivateAuras.icon or {}
				return icon.size or defIcon.size or 20
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.icon.size = clampNumber(value, 8, 120, pcfg.icon.size or 20)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasSize", pcfg.icon.size, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Icon direction"] or "Icon direction",
			kind = SettingType.Dropdown,
			field = "privateAurasPoint",
			parentId = "privateAuras",
			values = privateAuraPointOptions,
			height = 120,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local icon = pcfg.icon or {}
				local defIcon = defPrivateAuras.icon or {}
				return icon.point or defIcon.point or "LEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.icon.point = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Icon spacing"] or "Icon spacing",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasOffset",
			parentId = "privateAuras",
			minValue = 0,
			maxValue = 20,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local icon = pcfg.icon or {}
				local defIcon = defPrivateAuras.icon or {}
				return icon.offset or defIcon.offset or 2
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.icon.offset = clampNumber(value, 0, 20, pcfg.icon.offset or 2)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasOffset", pcfg.icon.offset, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Anchor point"] or "Anchor point",
			kind = SettingType.Dropdown,
			field = "privateAurasParentPoint",
			parentId = "privateAuras",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local parentCfg = pcfg.parent or {}
				local defParent = defPrivateAuras.parent or {}
				return parentCfg.point or defParent.point or "CENTER"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.parent.point = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasParentPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Anchor offset X"] or "Anchor offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasParentOffsetX",
			parentId = "privateAuras",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local parentCfg = pcfg.parent or {}
				local defParent = defPrivateAuras.parent or {}
				return parentCfg.offsetX or defParent.offsetX or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.parent.offsetX = clampNumber(value, -200, 200, pcfg.parent.offsetX or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasParentOffsetX", pcfg.parent.offsetX, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Anchor offset Y"] or "Anchor offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasParentOffsetY",
			parentId = "privateAuras",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local parentCfg = pcfg.parent or {}
				local defParent = defPrivateAuras.parent or {}
				return parentCfg.offsetY or defParent.offsetY or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.parent.offsetY = clampNumber(value, -200, 200, pcfg.parent.offsetY or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasParentOffsetY", pcfg.parent.offsetY, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Show countdown frame"] or "Show countdown frame",
			kind = SettingType.Checkbox,
			field = "privateAurasCountdownFrame",
			parentId = "privateAuras",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local defValue = defPrivateAuras.countdownFrame ~= false
				if pcfg.countdownFrame == nil then return defValue end
				return pcfg.countdownFrame ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.countdownFrame = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasCountdownFrame", pcfg.countdownFrame, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Show countdown numbers"] or "Show countdown numbers",
			kind = SettingType.Checkbox,
			field = "privateAurasCountdownNumbers",
			parentId = "privateAuras",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local defValue = defPrivateAuras.countdownNumbers ~= false
				if pcfg.countdownNumbers == nil then return defValue end
				return pcfg.countdownNumbers ~= false
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.countdownNumbers = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasCountdownNumbers", pcfg.countdownNumbers, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Text scale"] or "Text scale",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasTextScale",
			parentId = "privateAuras",
			minValue = 1,
			maxValue = 2.5,
			valueStep = 0.05,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local defValue = tonumber(defPrivateAuras.textScale) or 1
				return tonumber(pcfg.textScale) or defValue
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.textScale = clampNumber(value, 1, 2.5, tonumber(pcfg.textScale) or 1)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasTextScale", pcfg.textScale, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			formatter = formatSliderDecimal,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Show dispel type"] or "Show dispel type",
			kind = SettingType.Checkbox,
			field = "privateAurasShowDispelType",
			parentId = "privateAuras",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local defValue = defPrivateAuras.showDispelType == true
				if pcfg.showDispelType == nil then return defValue end
				return pcfg.showDispelType == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.showDispelType = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasShowDispelType", pcfg.showDispelType, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Show duration"] or "Show duration",
			kind = SettingType.Checkbox,
			field = "privateAurasDurationEnabled",
			parentId = "privateAuras",
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local durationCfg = pcfg.duration or {}
				local defDuration = defPrivateAuras.duration or {}
				if durationCfg.enable == nil then return defDuration.enable == true end
				return durationCfg.enable == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.duration.enable = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasDurationEnabled", pcfg.duration.enable, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = isPrivateAurasEnabled,
		},
		{
			name = L["Duration anchor"] or "Duration anchor",
			kind = SettingType.Dropdown,
			field = "privateAurasDurationPoint",
			parentId = "privateAuras",
			values = GF._sharedEdit.auraAnchorOpts,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local durationCfg = pcfg.duration or {}
				local defDuration = defPrivateAuras.duration or {}
				return durationCfg.point or defDuration.point or "BOTTOM"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.duration.point = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasDurationPoint", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				return isPrivateAurasEnabled() and (getCfg(kind) and getCfg(kind).privateAuras and getCfg(kind).privateAuras.duration and getCfg(kind).privateAuras.duration.enable == true)
			end,
		},
		{
			name = L["Duration offset X"] or "Duration offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasDurationOffsetX",
			parentId = "privateAuras",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local durationCfg = pcfg.duration or {}
				local defDuration = defPrivateAuras.duration or {}
				return durationCfg.offsetX or defDuration.offsetX or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.duration.offsetX = clampNumber(value, -50, 50, pcfg.duration.offsetX or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasDurationOffsetX", pcfg.duration.offsetX, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				return isPrivateAurasEnabled() and (getCfg(kind) and getCfg(kind).privateAuras and getCfg(kind).privateAuras.duration and getCfg(kind).privateAuras.duration.enable == true)
			end,
		},
		{
			name = L["Duration offset Y"] or "Duration offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "privateAurasDurationOffsetY",
			parentId = "privateAuras",
			minValue = -50,
			maxValue = 50,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local pcfg = cfg and cfg.privateAuras or {}
				local durationCfg = pcfg.duration or {}
				local defDuration = defPrivateAuras.duration or {}
				return durationCfg.offsetY or defDuration.offsetY or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				local pcfg = ensurePrivateAuraConfig(cfg)
				if not pcfg then return end
				pcfg.duration.offsetY = clampNumber(value, -50, 50, pcfg.duration.offsetY or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "privateAurasDurationOffsetY", pcfg.duration.offsetY, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function()
				return isPrivateAurasEnabled() and (getCfg(kind) and getCfg(kind).privateAuras and getCfg(kind).privateAuras.duration and getCfg(kind).privateAuras.duration.enable == true)
			end,
		},
	}

	if kind == "party" then
		settings[#settings + 1] = {
			name = L["Party"] or "Party",
			kind = SettingType.Collapsible,
			id = "party",
			defaultCollapsed = true,
		}
		settings[#settings + 1] = {
			name = L["Show player"] or "Show player",
			kind = SettingType.Checkbox,
			field = "showPlayer",
			default = (DEFAULTS.party and DEFAULTS.party.showPlayer) or false,
			parentId = "party",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.showPlayer == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.showPlayer = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "showPlayer", cfg.showPlayer, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		}
		settings[#settings + 1] = {
			name = L["Show solo"] or "Show solo",
			kind = SettingType.Checkbox,
			field = "showSolo",
			default = (DEFAULTS.party and DEFAULTS.party.showSolo) or false,
			parentId = "party",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.showSolo == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.showSolo = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "showSolo", cfg.showSolo, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		}
		settings[#settings + 1] = {
			name = L["Sort method"] or "Sort method",
			kind = SettingType.Dropdown,
			field = "sortMethod",
			parentId = "party",
			default = (DEFAULTS.party and DEFAULTS.party.sortMethod) or "INDEX",
			customDefaultText = getSortMethodLabel(),
			get = function() return getSortMethodValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				local v = tostring(value):upper()
				local custom = GFH.EnsureCustomSortConfig(cfg)
				if v == "CUSTOM" then
					custom.enabled = true
					cfg.sortMethod = "NAMELIST"
					if EditMode and EditMode.SetValue then
						EditMode:SetValue(editModeId, "sortMethod", "CUSTOM", nil, true)
						EditMode:SetValue(editModeId, "customSortEnabled", true, nil, true)
					end
				else
					custom.enabled = false
					cfg.sortMethod = v
					if EditMode and EditMode.SetValue then
						EditMode:SetValue(editModeId, "sortMethod", cfg.sortMethod, nil, true)
						EditMode:SetValue(editModeId, "customSortEnabled", false, nil, true)
					end
				end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
			end,
			generator = function(_, root, data)
				for _, option in ipairs(sortMethodOptions) do
					root:CreateRadio(option.label, function() return getSortMethodValue() == option.value end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local v = tostring(option.value):upper()
						local custom = GFH.EnsureCustomSortConfig(cfg)
						if v == "CUSTOM" then
							custom.enabled = true
							cfg.sortMethod = "NAMELIST"
							if EditMode and EditMode.SetValue then
								EditMode:SetValue(editModeId, "sortMethod", "CUSTOM", nil, true)
								EditMode:SetValue(editModeId, "customSortEnabled", true, nil, true)
							end
						else
							custom.enabled = false
							cfg.sortMethod = v
							if EditMode and EditMode.SetValue then
								EditMode:SetValue(editModeId, "sortMethod", cfg.sortMethod, nil, true)
								EditMode:SetValue(editModeId, "customSortEnabled", false, nil, true)
							end
						end
						GF:ApplyHeaderAttributes(kind)
						if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
						data.customDefaultText = option.label
						if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
					end)
				end
			end,
		}
		settings[#settings + 1] = {
			name = L["Sort direction"] or "Sort direction",
			kind = SettingType.Dropdown,
			field = "sortDir",
			parentId = "party",
			default = (DEFAULTS.party and DEFAULTS.party.sortDir) or "ASC",
			customDefaultText = getSortDirLabel(),
			get = function() return getSortDirValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				cfg.sortDir = tostring(value):upper()
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "sortDir", cfg.sortDir, nil, true) end
				GF:RefreshCustomSortNameList(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
			end,
			generator = function(_, root, data)
				for _, option in ipairs(sortDirOptions) do
					root:CreateRadio(option.label, function() return getSortDirValue() == option.value end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.sortDir = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "sortDir", cfg.sortDir, nil, true) end
						GF:RefreshCustomSortNameList(kind)
						if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
						data.customDefaultText = option.label
						if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
					end)
				end
			end,
		}
		settings[#settings + 1] = {
			name = L["UFGroupCustomSortPlayerFirstInRole"] or "Player first in role",
			kind = SettingType.Checkbox,
			field = "customSortPlayerFirstInRole",
			parentId = "party",
			default = false,
			get = function()
				local cfg = getCfg(kind)
				local custom = cfg and GFH.EnsureCustomSortConfig(cfg)
				return custom and custom.playerFirstInRole == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local custom = GFH.EnsureCustomSortConfig(cfg)
				custom.playerFirstInRole = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "customSortPlayerFirstInRole", custom.playerFirstInRole, nil, true) end
				GF:RefreshCustomSortNameList(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
			end,
			isEnabled = function() return isCustomSortingEnabled() end,
		}
	elseif raidLikeKind then
		local raidSectionName = (kind == "mt" and "Main Tank") or (kind == "ma" and "Main Assist") or (RAID or "Raid")
		settings[#settings + 1] = {
			name = raidSectionName,
			kind = SettingType.Collapsible,
			id = "raid",
			defaultCollapsed = true,
		}
		if splitRoleKind then
			settings[#settings + 1] = {
				name = L["Show for role"] or "Show for role",
				kind = SettingType.Dropdown,
				field = "viewerRoleFilter",
				parentId = "raid",
				default = (DEFAULTS[kind] and DEFAULTS[kind].viewerRoleFilter) or "ALL",
				customDefaultText = getSplitRoleViewerRoleLabel(),
				get = function() return getSplitRoleViewerRoleValue() end,
				set = function(_, value)
					local cfg = getCfg(kind)
					if not cfg then return end
					cfg.viewerRoleFilter = GF.NormalizeViewerRoleFilter(value)
					if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "viewerRoleFilter", cfg.viewerRoleFilter, nil, true) end
					GF:ApplyHeaderAttributes(kind)
					if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				end,
				generator = GF.DropdownRadioGenerator(GF.splitRoleViewerRoleOptions),
			}
		end
		settings[#settings + 1] = {
			name = L["Units per column"] or "Units per column",
			kind = SettingType.Slider,
			allowInput = true,
			field = "unitsPerColumn",
			minValue = 1,
			maxValue = 10,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].unitsPerColumn) or (DEFAULTS.raid and DEFAULTS.raid.unitsPerColumn) or 5,
			parentId = "raid",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.unitsPerColumn or (DEFAULTS[kind] and DEFAULTS[kind].unitsPerColumn) or (DEFAULTS.raid and DEFAULTS.raid.unitsPerColumn) or 5
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local v = clampNumber(value, 1, 10, cfg.unitsPerColumn or 5)
				v = floor(v + 0.5)
				cfg.unitsPerColumn = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "unitsPerColumn", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		}
		settings[#settings + 1] = {
			name = L["Max columns"] or "Max columns",
			kind = SettingType.Slider,
			allowInput = true,
			field = "maxColumns",
			minValue = 1,
			maxValue = 10,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].maxColumns) or (DEFAULTS.raid and DEFAULTS.raid.maxColumns) or 8,
			parentId = "raid",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.maxColumns or (DEFAULTS[kind] and DEFAULTS[kind].maxColumns) or (DEFAULTS.raid and DEFAULTS.raid.maxColumns) or 8
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local v = clampNumber(value, 1, 10, cfg.maxColumns or 8)
				v = floor(v + 0.5)
				cfg.maxColumns = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "maxColumns", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		}
		settings[#settings + 1] = {
			name = L["Preserve content size"] or "Preserve content size",
			kind = SettingType.Slider,
			allowInput = true,
			field = "dynamicScaleMin",
			minValue = 0,
			maxValue = 1,
			valueStep = 0.05,
			formatter = function(value)
				if (tonumber(value) or 0) <= 0 then return L["Off"] or "Off" end
				return formatSliderDecimal(value)
			end,
			default = (DEFAULTS.raid and DEFAULTS.raid.dynamicScaleMin) or 0,
			parentId = "raid",
			get = function()
				local cfg = getCfg(kind)
				return GF.GetDynamicContentScaleAmount(cfg or DEFAULTS.raid)
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.dynamicScaleMin = clampNumber(value, 0, 1, cfg.dynamicScaleMin or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "dynamicScaleMin", cfg.dynamicScaleMin, nil, true) end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				refreshAllPrivateAuras()
				GF:RefreshGroupIndicators()
			end,
			isShown = function() return kind == "raid" end,
		}
		settings[#settings + 1] = {
			name = L["Column spacing"] or "Column spacing",
			kind = SettingType.Slider,
			allowInput = true,
			field = "columnSpacing",
			minValue = 0,
			maxValue = 40,
			valueStep = 1,
			default = (DEFAULTS[kind] and DEFAULTS[kind].columnSpacing) or (DEFAULTS.raid and DEFAULTS.raid.columnSpacing) or 0,
			parentId = "raid",
			get = function()
				local cfg = getCfg(kind)
				return cfg and cfg.columnSpacing or (DEFAULTS[kind] and DEFAULTS[kind].columnSpacing) or (DEFAULTS.raid and DEFAULTS.raid.columnSpacing) or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local v = clampNumber(value, 0, 40, cfg.columnSpacing or 0)
				cfg.columnSpacing = v
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "columnSpacing", v, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
		}
		settings[#settings + 1] = {
			name = L["Group by"] or "Group by",
			kind = SettingType.Dropdown,
			field = "groupBy",
			parentId = "raid",
			default = (DEFAULTS.raid and DEFAULTS.raid.groupBy) or "GROUP",
			customDefaultText = getGroupByLabel(),
			get = function() return getGroupByValue() end,
			set = function(_, value)
				if not value then return end
				applyGroupByPreset(value)
			end,
			isShown = function() return raidKind end,
			isEnabled = function() return not isCustomSortEditorOpen() end,
			generator = function(_, root, data)
				for _, option in ipairs(sortGroupOptions) do
					root:CreateRadio(option.label, function() return getGroupByValue() == option.value end, function()
						applyGroupByPreset(option.value)
						data.customDefaultText = option.label
						if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
					end)
				end
			end,
		}
		settings[#settings + 1] = {
			name = L["Sort method"] or "Sort method",
			kind = SettingType.Dropdown,
			field = "sortMethod",
			parentId = "raid",
			default = (DEFAULTS.raid and DEFAULTS.raid.sortMethod) or "INDEX",
			customDefaultText = getSortMethodLabel(),
			get = function() return getSortMethodValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				local v = tostring(value):upper()
				local custom = GFH.EnsureCustomSortConfig(cfg)
				if v == "CUSTOM" then
					custom.enabled = true
					cfg.sortMethod = "NAMELIST"
					if EditMode and EditMode.SetValue then
						EditMode:SetValue(editModeId, "sortMethod", "CUSTOM", nil, true)
						EditMode:SetValue(editModeId, "customSortEnabled", true, nil, true)
					end
				else
					custom.enabled = false
					cfg.sortMethod = v
					if EditMode and EditMode.SetValue then
						EditMode:SetValue(editModeId, "sortMethod", cfg.sortMethod, nil, true)
						EditMode:SetValue(editModeId, "customSortEnabled", false, nil, true)
					end
					if GF._customSortEditor and GF._customSortEditor.IsShown and GF._customSortEditor:IsShown() then GF._customSortEditor:Hide() end
				end
				GF:ApplyHeaderAttributes(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
			end,
			isShown = function() return raidKind end,
			generator = function(_, root, data)
				for _, option in ipairs(sortMethodOptions) do
					root:CreateRadio(option.label, function() return getSortMethodValue() == option.value end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						local v = tostring(option.value):upper()
						local custom = GFH.EnsureCustomSortConfig(cfg)
						if v == "CUSTOM" then
							custom.enabled = true
							cfg.sortMethod = "NAMELIST"
							if EditMode and EditMode.SetValue then
								EditMode:SetValue(editModeId, "sortMethod", "CUSTOM", nil, true)
								EditMode:SetValue(editModeId, "customSortEnabled", true, nil, true)
							end
						else
							custom.enabled = false
							cfg.sortMethod = v
							if EditMode and EditMode.SetValue then
								EditMode:SetValue(editModeId, "sortMethod", cfg.sortMethod, nil, true)
								EditMode:SetValue(editModeId, "customSortEnabled", false, nil, true)
							end
							if GF._customSortEditor and GF._customSortEditor.IsShown and GF._customSortEditor:IsShown() then GF._customSortEditor:Hide() end
						end
						GF:ApplyHeaderAttributes(kind)
						if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
						data.customDefaultText = option.label
						if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
					end)
				end
			end,
		}
		settings[#settings + 1] = {
			name = L["Sort direction"] or "Sort direction",
			kind = SettingType.Dropdown,
			field = "sortDir",
			parentId = "raid",
			default = (DEFAULTS.raid and DEFAULTS.raid.sortDir) or "ASC",
			customDefaultText = getSortDirLabel(),
			get = function() return getSortDirValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg or not value then return end
				cfg.sortDir = tostring(value):upper()
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "sortDir", cfg.sortDir, nil, true) end
				GF:RefreshCustomSortNameList(kind)
			end,
			isShown = function() return raidKind end,
			generator = function(_, root, data)
				for _, option in ipairs(sortDirOptions) do
					root:CreateRadio(option.label, function() return getSortDirValue() == option.value end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.sortDir = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "sortDir", cfg.sortDir, nil, true) end
						GF:RefreshCustomSortNameList(kind)
						data.customDefaultText = option.label
						if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
					end)
				end
			end,
		}
		settings[#settings + 1] = {
			name = L["UFGroupCustomSortPlayerFirstInRole"] or "Player first in role",
			kind = SettingType.Checkbox,
			field = "customSortPlayerFirstInRole",
			parentId = "raid",
			default = false,
			get = function()
				local cfg = getCfg(kind)
				local custom = cfg and GFH.EnsureCustomSortConfig(cfg)
				return custom and custom.playerFirstInRole == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local custom = GFH.EnsureCustomSortConfig(cfg)
				custom.playerFirstInRole = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "customSortPlayerFirstInRole", custom.playerFirstInRole, nil, true) end
				GF:RefreshCustomSortNameList(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
			end,
			isShown = function() return raidKind end,
			isEnabled = function() return isCustomSortingEnabled() end,
		}
		settings[#settings + 1] = {
			name = L["Separate Melee & Ranged DPS"] or "Separate Melee & Ranged DPS",
			kind = SettingType.Checkbox,
			field = "customSortSeparateMeleeRanged",
			parentId = "raid",
			default = false,
			get = function()
				local cfg = getCfg(kind)
				local custom = cfg and GFH.EnsureCustomSortConfig(cfg)
				return custom and custom.separateMeleeRanged == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				local custom = GFH.EnsureCustomSortConfig(cfg)
				custom.separateMeleeRanged = value and true or false
				if custom.separateMeleeRanged then
					custom.roleOrder = GFH.ExpandRoleOrder(custom.roleOrder, true)
				else
					custom.roleOrder = GFH.CollapseRoleOrder(custom.roleOrder)
				end
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "customSortSeparateMeleeRanged", custom.separateMeleeRanged, nil, true) end
				GF:RefreshCustomSortNameList(kind)
				if GF._previewActive and GF._previewActive[kind] then GF:UpdatePreviewLayout(kind) end
				if GF._customSortEditor and GF._customSortEditor.Refresh then GF._customSortEditor:Refresh() end
			end,
			isShown = function() return raidKind end,
			isEnabled = function() return isCustomSortingEnabled() end,
		}
		settings[#settings + 1] = {
			name = "",
			kind = SettingType.Divider,
			parentId = "raid",
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Show indicator"] or "Show indicator",
			kind = SettingType.Checkbox,
			field = "groupIndicatorEnabled",
			parentId = "raid",
			get = function() return getGroupIndicatorEnabledValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.enabled = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorEnabled", cfg.groupIndicator.enabled, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Hide Group number per Frame"] or "Hide Group number per Frame",
			kind = SettingType.Checkbox,
			field = "groupIndicatorHidePerFrame",
			parentId = "raid",
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local gi = cfg and cfg.groupIndicator or {}
				local defGI = def.groupIndicator or {}
				if gi.hidePerFrame == nil then return defGI.hidePerFrame == true end
				return gi.hidePerFrame == true
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.hidePerFrame = value and true or false
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorHidePerFrame", cfg.groupIndicator.hidePerFrame, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Format"] or "Format",
			kind = SettingType.Dropdown,
			field = "groupIndicatorFormat",
			parentId = "raid",
			customDefaultText = getGroupIndicatorFormatLabel(),
			get = function() return getGroupIndicatorFormatValue() end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.format = normalizeGroupNumberFormat(value) or "GROUP"
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorFormat", cfg.groupIndicator.format, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root, data)
				for _, option in ipairs(GF._groupNumberFormatCache.opts) do
					local value = option and option.value
					if value ~= nil then
						local label = option.label or option.text or tostring(value)
						root:CreateRadio(label, function() return getGroupIndicatorFormatValue() == value end, function()
							local cfg = getCfg(kind)
							if not cfg then return end
							cfg.groupIndicator = cfg.groupIndicator or {}
							cfg.groupIndicator.format = value
							if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorFormat", value, nil, true) end
							data.customDefaultText = label
							if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RequestRefreshSettings then addon.EditModeLib.internal:RequestRefreshSettings() end
							GF:ApplyHeaderAttributes(kind)
						end)
					end
				end
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = COLOR,
			kind = SettingType.Color,
			field = "groupIndicatorColor",
			parentId = "raid",
			hasOpacity = true,
			default = (DEFAULTS[kind] and DEFAULTS[kind].groupIndicator and DEFAULTS[kind].groupIndicator.color) or { 1, 1, 1, 1 },
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
				local r, g, b, a = unpackColor(style.color, GFH.COLOR_WHITE)
				return { r = r, g = g, b = b, a = a }
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not (cfg and value) then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.color = { value.r or 1, value.g or 1, value.b or 1, value.a or 1 }
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorColor", cfg.groupIndicator.color, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = FONT_SIZE,
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupIndicatorFontSize",
			parentId = "raid",
			minValue = 8,
			maxValue = 100,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
				return style.fontSize or 12
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.fontSize = clampNumber(value, 8, 100, cfg.groupIndicator.fontSize or 12)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorFontSize", cfg.groupIndicator.fontSize, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Font"] or "Font",
			kind = SettingType.Dropdown,
			field = "groupIndicatorFont",
			height = FONT_DROPDOWN_SCROLL_HEIGHT,
			parentId = "raid",
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
				return style.font or nil
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.font = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorFont", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(fontOptions()) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local def = DEFAULTS[kind] or {}
						local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
						return (style.font or nil) == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.groupIndicator = cfg.groupIndicator or {}
						cfg.groupIndicator.font = option.value
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorFont", option.value, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Font outline"] or "Font outline",
			kind = SettingType.Dropdown,
			field = "groupIndicatorFontOutline",
			parentId = "raid",
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
				return normalizeFontStyleChoice(style.fontOutline, "OUTLINE")
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.fontOutline = normalizeFontStyleChoice(value, "OUTLINE")
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorFontOutline", cfg.groupIndicator.fontOutline, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			generator = function(_, root)
				for _, option in ipairs(GF._sharedEdit.outlineOpts) do
					root:CreateRadio(option.label, function()
						local cfg = getCfg(kind)
						local def = DEFAULTS[kind] or {}
						local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
						return normalizeFontStyleChoice(style.fontOutline, "OUTLINE") == option.value
					end, function()
						local cfg = getCfg(kind)
						if not cfg then return end
						cfg.groupIndicator = cfg.groupIndicator or {}
						cfg.groupIndicator.fontOutline = normalizeFontStyleChoice(option.value, "OUTLINE")
						if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorFontOutline", cfg.groupIndicator.fontOutline, nil, true) end
						GF:ApplyHeaderAttributes(kind)
					end)
				end
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Anchor"] or "Anchor",
			kind = SettingType.Dropdown,
			field = "groupIndicatorAnchor",
			parentId = "raid",
			values = GF._sharedEdit.anchorOpts9,
			height = 180,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
				return style.anchor or "TOPLEFT"
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.anchor = value
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorAnchor", value, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Offset X"] or "Offset X",
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupIndicatorOffsetX",
			parentId = "raid",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
				local off = style.offset or {}
				return off.x or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.offset = cfg.groupIndicator.offset or {}
				cfg.groupIndicator.offset.x = clampNumber(value, -200, 200, (cfg.groupIndicator.offset and cfg.groupIndicator.offset.x) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorOffsetX", cfg.groupIndicator.offset.x, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = L["Offset Y"] or "Offset Y",
			kind = SettingType.Slider,
			allowInput = true,
			field = "groupIndicatorOffsetY",
			parentId = "raid",
			minValue = -200,
			maxValue = 200,
			valueStep = 1,
			get = function()
				local cfg = getCfg(kind)
				local def = DEFAULTS[kind] or {}
				local style = resolveGroupIndicatorStyle(cfg, def, (cfg and cfg.health) or {})
				local off = style.offset or {}
				return off.y or 0
			end,
			set = function(_, value)
				local cfg = getCfg(kind)
				if not cfg then return end
				cfg.groupIndicator = cfg.groupIndicator or {}
				cfg.groupIndicator.offset = cfg.groupIndicator.offset or {}
				cfg.groupIndicator.offset.y = clampNumber(value, -200, 200, (cfg.groupIndicator.offset and cfg.groupIndicator.offset.y) or 0)
				if EditMode and EditMode.SetValue then EditMode:SetValue(editModeId, "groupIndicatorOffsetY", cfg.groupIndicator.offset.y, nil, true) end
				GF:ApplyHeaderAttributes(kind)
			end,
			isEnabled = function() return isGroupIndicatorSettingsEnabled() end,
			isShown = function() return isGroupIndicatorShown() end,
		}
		settings[#settings + 1] = {
			name = "",
			kind = SettingType.Divider,
			parentId = "raid",
			isShown = function() return isRaidGroupFilterShown() end,
		}
		for group = 1, 8 do
			local raidGroup = group
			settings[#settings + 1] = {
				name = string.format(L["UFGroupShowRaidGroup"] or "Show group %d", raidGroup),
				kind = SettingType.Checkbox,
				field = "raidGroup" .. raidGroup,
				parentId = "raid",
				default = true,
				get = function()
					local cfg = getCfg(kind)
					return GF.IsRaidGroupShown(cfg, raidGroup)
				end,
				set = function(_, value) updateRaidGroupFilter(raidGroup, value) end,
				isShown = function() return isRaidGroupFilterShown() end,
				isEnabled = function()
					local cfg = getCfg(kind)
					local selected, count = GF.GetSelectedRaidGroups(cfg)
					return count > 1 or selected[raidGroup] ~= true
				end,
			}
		end
	end

	if kind == "party" or raidLikeKind then
		local raidMarkerIndex
		for i, setting in ipairs(settings) do
			if setting and setting.id == "raidmarker" then
				raidMarkerIndex = i
				break
			end
		end
		GF:AppendStatusIconSettings(settings, kind, editModeId, raidMarkerIndex)
	end

	do
		local sectionByParent = {
			buffs = "buff",
			debuffs = "debuff",
			externals = "externals",
		}
		local enableFieldByParent = {
			buffs = "buffsEnabled",
			debuffs = "debuffsEnabled",
			externals = "externalsEnabled",
		}
		local function isAuraSectionEnabled(typeKey)
			local typeCfg = getAuraTypeConfig(typeKey)
			return typeCfg and typeCfg.enabled == true
		end
		for _, setting in ipairs(settings) do
			local typeKey = setting and sectionByParent[setting.parentId]
			if typeKey and setting.field ~= enableFieldByParent[setting.parentId] then
				local previousIsEnabled = setting.isEnabled
				setting.isEnabled = function()
					if not isAuraSectionEnabled(typeKey) then return false end
					if previousIsEnabled then return previousIsEnabled() ~= false end
					return true
				end
			end
		end
	end

	settings = GF.ReorderGroupEditModeSettings(settings)
	GF.ApplyBlizzardAuraSettingVisibility(settings, kind)
	return settings
end

function GF._syncGroupEditModeLayoutData(kind, editModeId, layoutName)
	if not (kind and editModeId) then return end
	if not (EditMode and EditMode.EnsureLayoutData and EditMode.GetActiveLayoutName) then return end

	layoutName = layoutName or EditMode:GetActiveLayoutName()
	if not layoutName or layoutName == "" then return end

	local data = EditMode:EnsureLayoutData(editModeId, layoutName)
	if type(data) ~= "table" then return end
	local cfg = getCfg(kind)
	local def = DEFAULTS[kind] or {}
	local point = tostring((cfg and cfg.point) or def.point or data.point or "CENTER"):upper()
	local relativePoint = tostring((cfg and (cfg.relativePoint or cfg.point)) or def.relativePoint or def.point or point):upper()
	local anchorTarget = GF.GetGroupAnchorTargetValue(kind)
	local x = clampNumber((cfg and cfg.x) or def.x or data.x or 0, -4000, 4000, 0)
	local y = clampNumber((cfg and cfg.y) or def.y or data.y or 0, -4000, 4000, 0)

	data.anchorTarget = anchorTarget
	if EditMode.SetValue then
		EditMode:SetValue(editModeId, "point", point, layoutName, true)
		EditMode:SetValue(editModeId, "relativePoint", relativePoint, layoutName, true)
		EditMode:SetValue(editModeId, "x", x, layoutName, true)
		EditMode:SetValue(editModeId, "y", y, layoutName, true)
	else
		data.point = point
		data.relativePoint = relativePoint
		data.x = x
		data.y = y
	end
end

function GF.SyncGroupEditModePositionValues(kind, editModeId, cfg, layoutName)
	if not (kind and editModeId and cfg and EditMode and EditMode.SetValue) then return end
	local def = DEFAULTS[kind] or {}
	local point = tostring(cfg.point or def.point or "CENTER"):upper()
	local relativePoint = tostring(cfg.relativePoint or cfg.point or def.relativePoint or def.point or point):upper()
	local x = clampNumber(cfg.x ~= nil and cfg.x or def.x or 0, -4000, 4000, 0)
	local y = clampNumber(cfg.y ~= nil and cfg.y or def.y or 0, -4000, 4000, 0)

	cfg.point = point
	cfg.relativePoint = relativePoint
	cfg.x = x
	cfg.y = y
	EditMode:SetValue(editModeId, "point", point, layoutName, true)
	EditMode:SetValue(editModeId, "relativePoint", relativePoint, layoutName, true)
	EditMode:SetValue(editModeId, "x", x, layoutName, true)
	EditMode:SetValue(editModeId, "y", y, layoutName, true)
end

function GF.SyncAllGroupEditModeLayoutData(layoutName)
	if not EDITMODE_IDS then return end
	for _, kind in ipairs({ "party", "raid", "mt", "ma" }) do
		local editModeId = EDITMODE_IDS[kind]
		if editModeId then GF._syncGroupEditModeLayoutData(kind, editModeId, layoutName) end
	end
end

local function applyEditModeData(kind, data)
	if not data then return end
	-- EditMode may fire apply callbacks during login/reload/profile refresh with
	-- layout payloads that are not UF-profile isolated. Only persist while in Edit Mode.
	if not isEditModeActive() then return end
	local cfg = getCfg(kind)
	if not cfg then return end

	local positionChanged = false
	if data.anchorTarget ~= nil then
		local target = GF.ValidateGroupAnchorTarget(kind, data.anchorTarget, cfg.relativeTo, true)
		if cfg.relativeTo ~= target then
			cfg.relativeTo = target
			positionChanged = true
		end
		if data.anchorTarget ~= target then
			data.anchorTarget = target
			if EditMode and EditMode.SetValue then EditMode:SetValue(EDITMODE_IDS[kind], "anchorTarget", target, nil, true) end
		end
	end
	if data.point or data.relativePoint or data.x ~= nil or data.y ~= nil then
		local oldPoint, oldRelativePoint, oldX, oldY = cfg.point, cfg.relativePoint, cfg.x, cfg.y
		local newPoint = tostring(data.point or cfg.point or "CENTER"):upper()
		local newRelativePoint = tostring(data.relativePoint or newPoint):upper()
		local newX = clampNumber((data.x ~= nil and data.x or cfg.x) or 0, -4000, 4000, cfg.x or 0)
		local newY = clampNumber((data.y ~= nil and data.y or cfg.y) or 0, -4000, 4000, cfg.y or 0)

		cfg.point = newPoint
		cfg.relativePoint = newRelativePoint
		cfg.relativeTo = GF.ValidateGroupAnchorTarget(kind, cfg.relativeTo, cfg.relativeTo, true)
		cfg.x = newX
		cfg.y = newY
		positionChanged = positionChanged
			or oldPoint ~= cfg.point
			or oldRelativePoint ~= cfg.relativePoint
			or oldX ~= cfg.x
			or oldY ~= cfg.y
		if data.point ~= cfg.point then
			data.point = cfg.point
			if EditMode and EditMode.SetValue then EditMode:SetValue(EDITMODE_IDS[kind], "point", cfg.point, nil, true) end
		end
		if data.relativePoint ~= cfg.relativePoint then
			data.relativePoint = cfg.relativePoint
			if EditMode and EditMode.SetValue then EditMode:SetValue(EDITMODE_IDS[kind], "relativePoint", cfg.relativePoint, nil, true) end
		end
		if data.x ~= cfg.x then
			data.x = cfg.x
			if EditMode and EditMode.SetValue then EditMode:SetValue(EDITMODE_IDS[kind], "x", cfg.x, nil, true) end
		end
		if data.y ~= cfg.y then
			data.y = cfg.y
			if EditMode and EditMode.SetValue then EditMode:SetValue(EDITMODE_IDS[kind], "y", cfg.y, nil, true) end
		end
	end

	local refreshAuras = false
	if data.width ~= nil then cfg.width = clampNumber(data.width, 40, 600, cfg.width or 100) end
	if data.height ~= nil then cfg.height = clampNumber(data.height, 10, 200, cfg.height or 24) end
	if data.powerHeight ~= nil then cfg.powerHeight = clampNumber(data.powerHeight, 0, 50, cfg.powerHeight or 6) end
	if data.tooltipMode ~= nil or data.tooltipModifier ~= nil or data.tooltipUseEditMode ~= nil then cfg.tooltip = cfg.tooltip or {} end
	if data.tooltipMode ~= nil then cfg.tooltip.mode = tostring(data.tooltipMode):upper() end
	if data.tooltipModifier ~= nil then cfg.tooltip.modifier = tostring(data.tooltipModifier):upper() end
	if data.tooltipUseEditMode ~= nil then cfg.tooltip.useEditMode = data.tooltipUseEditMode and true or false end
	if data.hideInClientScene ~= nil then cfg.hideInClientScene = data.hideInClientScene and true or false end
	if data.tooltipAuras ~= nil then
		local ac = ensureAuraConfig(cfg)
		local enabled = data.tooltipAuras and true or false
		ac.buff.showTooltip = enabled
		ac.debuff.showTooltip = enabled
		ac.externals.showTooltip = enabled
		refreshAuras = true
	end
	if data.spacing ~= nil then cfg.spacing = clampNumber(data.spacing, 0, GROUP_FRAME_MAX_SPACING, cfg.spacing or 0) end
	if data.growth then
		local mode, baseGrowth = GF.ResolveUnitGrowthDirection(data.growth, cfg.growth or "DOWN")
		if not GF.SupportsCenterGrowth(kind) and (mode == "CENTER_HORIZONTAL" or mode == "CENTER_VERTICAL") then mode = baseGrowth end
		cfg.growth = mode or "DOWN"
	end
	if kind == "raid" and data.groupGrowth then
		local _, growth = GF.ResolveUnitGrowthDirection(cfg.growth, "DOWN")
		local defaultGroupGrowth = DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
		if GFH.ResolveGroupGrowthDirection then
			cfg.groupGrowth = GFH.ResolveGroupGrowthDirection(data.groupGrowth, growth, defaultGroupGrowth)
		else
			cfg.groupGrowth = (GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(data.groupGrowth, nil)) or ((growth == "RIGHT" or growth == "LEFT") and "DOWN" or "RIGHT")
		end
	end
	if kind == "raid" and data.groupsPerRow ~= nil then cfg.groupsPerRow = GF.NormalizeRaidGroupsPerRow(data.groupsPerRow, cfg.groupsPerRow or (DEFAULTS.raid and DEFAULTS.raid.groupsPerRow)) end
	if data.barTexture ~= nil then
		if data.barTexture == BAR_TEX_INHERIT then
			cfg.barTexture = BAR_TEX_INHERIT
		else
			cfg.barTexture = data.barTexture
		end
	end
	if
		data.borderEnabled ~= nil
		or data.borderColor ~= nil
		or data.borderTexture ~= nil
		or data.borderSize ~= nil
		or data.borderOffset ~= nil
		or data.borderStrata ~= nil
		or data.borderFrameLevelOffset ~= nil
	then
		cfg.border = cfg.border or {}
	end
	if data.borderEnabled ~= nil then cfg.border.enabled = data.borderEnabled and true or false end
	if data.borderColor ~= nil then cfg.border.color = data.borderColor end
	if data.borderTexture ~= nil then cfg.border.texture = data.borderTexture end
	if data.borderSize ~= nil then cfg.border.edgeSize = data.borderSize end
	if data.borderOffset ~= nil then cfg.border.offset = data.borderOffset end
	if data.borderStrata ~= nil then
		local strata = tostring(data.borderStrata or ""):upper()
		if strata == "DEFAULT" then strata = "" end
		if
			strata ~= ""
			and strata ~= "BACKGROUND"
			and strata ~= "LOW"
			and strata ~= "MEDIUM"
			and strata ~= "HIGH"
			and strata ~= "DIALOG"
			and strata ~= "FULLSCREEN"
			and strata ~= "FULLSCREEN_DIALOG"
			and strata ~= "TOOLTIP"
		then
			strata = ""
		end
		cfg.border.strata = (strata ~= "") and strata or nil
	end
	if data.borderFrameLevelOffset ~= nil then
		local offset = clampNumber(data.borderFrameLevelOffset, -20, 1000, (cfg.border and cfg.border.frameLevelOffset) or 3)
		offset = floor(offset + (offset >= 0 and 0.5 or -0.5))
		cfg.border.frameLevelOffset = offset
	end
	if kind == "party" and (data.groupBorderEnabled ~= nil or data.groupBorderColor ~= nil or data.groupBorderTexture ~= nil or data.groupBorderSize ~= nil or data.groupBorderOffset ~= nil) then
		cfg.groupBorder = cfg.groupBorder or {}
		if data.groupBorderEnabled ~= nil then cfg.groupBorder.enabled = data.groupBorderEnabled and true or false end
		if data.groupBorderColor ~= nil then cfg.groupBorder.color = data.groupBorderColor end
		if data.groupBorderTexture ~= nil then cfg.groupBorder.texture = data.groupBorderTexture end
		if data.groupBorderSize ~= nil then cfg.groupBorder.edgeSize = clampNumber(data.groupBorderSize, 1, 64, cfg.groupBorder.edgeSize or 1) end
		if data.groupBorderOffset ~= nil then cfg.groupBorder.offset = clampNumber(data.groupBorderOffset, 0, 64, cfg.groupBorder.offset or cfg.groupBorder.inset or 0) end
	end
	if
		data.hoverHighlightEnabled ~= nil
		or data.hoverHighlightColor ~= nil
		or data.hoverHighlightTexture ~= nil
		or data.hoverHighlightStrata ~= nil
		or data.hoverHighlightFrameLevelOffset ~= nil
		or data.hoverHighlightSize ~= nil
		or data.hoverHighlightOffset ~= nil
	then
		cfg.highlightHover = cfg.highlightHover or {}
	end
	if data.hoverHighlightEnabled ~= nil then cfg.highlightHover.enabled = data.hoverHighlightEnabled and true or false end
	if data.hoverHighlightColor ~= nil then cfg.highlightHover.color = data.hoverHighlightColor end
	if data.hoverHighlightTexture ~= nil then cfg.highlightHover.texture = data.hoverHighlightTexture end
	if data.hoverHighlightStrata ~= nil then cfg.highlightHover.strata = GF.NormalizeFrameStrataToken(data.hoverHighlightStrata) end
	if data.hoverHighlightFrameLevelOffset ~= nil then
		local levelOffset = clampNumber(data.hoverHighlightFrameLevelOffset, -20, 1000, cfg.highlightHover.frameLevelOffset or 4)
		levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
		cfg.highlightHover.frameLevelOffset = levelOffset
	end
	if data.hoverHighlightSize ~= nil then cfg.highlightHover.size = clampNumber(data.hoverHighlightSize, 1, 64, cfg.highlightHover.size or 2) end
	if data.hoverHighlightOffset ~= nil then cfg.highlightHover.offset = clampNumber(data.hoverHighlightOffset, -64, 64, cfg.highlightHover.offset or 0) end
	if
		data.aggroHighlightEnabled ~= nil
		or data.aggroHighlightColor ~= nil
		or data.aggroHighlightMode ~= nil
		or data.aggroHighlightSample ~= nil
		or data.aggroHighlightTexture ~= nil
		or data.aggroHighlightLayer ~= nil
		or data.aggroHighlightSize ~= nil
		or data.aggroHighlightOffset ~= nil
	then
		cfg.highlightAggro = cfg.highlightAggro or {}
	end
	if data.aggroHighlightEnabled ~= nil then cfg.highlightAggro.enabled = data.aggroHighlightEnabled and true or false end
	if data.aggroHighlightColor ~= nil then cfg.highlightAggro.color = data.aggroHighlightColor end
	if data.aggroHighlightMode ~= nil then cfg.highlightAggro.mode = GF.NormalizeAggroHighlightMode(data.aggroHighlightMode) end
	if data.aggroHighlightSample ~= nil then cfg.highlightAggro.sample = data.aggroHighlightSample and true or false end
	if data.aggroHighlightTexture ~= nil then cfg.highlightAggro.texture = data.aggroHighlightTexture end
	if data.aggroHighlightLayer ~= nil then
		local layer = tostring(data.aggroHighlightLayer or cfg.highlightAggro.layer or "ABOVE_BORDER"):upper()
		if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
		cfg.highlightAggro.layer = layer
	end
	if data.aggroHighlightSize ~= nil then cfg.highlightAggro.size = clampNumber(data.aggroHighlightSize, 1, 64, cfg.highlightAggro.size or 2) end
	if data.aggroHighlightOffset ~= nil then cfg.highlightAggro.offset = clampNumber(data.aggroHighlightOffset, -64, 64, cfg.highlightAggro.offset or 0) end
	if
		data.targetHighlightEnabled ~= nil
		or data.targetHighlightColor ~= nil
		or data.targetHighlightTexture ~= nil
		or data.targetHighlightLayer ~= nil
		or data.targetHighlightSize ~= nil
		or data.targetHighlightOffset ~= nil
	then
		cfg.highlightTarget = cfg.highlightTarget or {}
	end
	if data.targetHighlightEnabled ~= nil then cfg.highlightTarget.enabled = data.targetHighlightEnabled and true or false end
	if data.targetHighlightColor ~= nil then cfg.highlightTarget.color = data.targetHighlightColor end
	if data.targetHighlightTexture ~= nil then cfg.highlightTarget.texture = data.targetHighlightTexture end
	if data.targetHighlightLayer ~= nil then
		local layer = tostring(data.targetHighlightLayer or cfg.highlightTarget.layer or "ABOVE_BORDER"):upper()
		if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
		cfg.highlightTarget.layer = layer
	end
	if data.targetHighlightSize ~= nil then cfg.highlightTarget.size = clampNumber(data.targetHighlightSize, 1, 64, cfg.highlightTarget.size or 2) end
	if data.targetHighlightOffset ~= nil then cfg.highlightTarget.offset = clampNumber(data.targetHighlightOffset, -64, 64, cfg.highlightTarget.offset or 0) end
	if data.showName ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.showName = data.showName and true or false
	end
	if data.nameClassColor ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.useClassColor = data.nameClassColor and true or false
		cfg.status = cfg.status or {}
		cfg.status.nameColorMode = data.nameClassColor and "CLASS" or "CUSTOM"
	end
	if data.nameAnchor ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.nameAnchor = data.nameAnchor
	end
	if data.nameOffsetX ~= nil or data.nameOffsetY ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.nameOffset = cfg.text.nameOffset or {}
		if data.nameOffsetX ~= nil then cfg.text.nameOffset.x = data.nameOffsetX end
		if data.nameOffsetY ~= nil then cfg.text.nameOffset.y = data.nameOffsetY end
	end
	if data.nameMaxChars ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.nameMaxChars = clampNumber(data.nameMaxChars, 0, 40, cfg.text.nameMaxChars or 0)
	end
	if data.nameNoEllipsis ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.nameNoEllipsis = data.nameNoEllipsis and true or false
	end
	if data.nameFontSize ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.fontSize = data.nameFontSize
	end
	if data.nameFont ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.font = (type(data.nameFont) == "string" and data.nameFont ~= "") and data.nameFont or GF.GlobalFontConfigKey()
	end
	if data.nameFontOutline ~= nil then
		cfg.text = cfg.text or {}
		cfg.text.fontOutline = data.nameFontOutline
	end
	if data.nameStrata ~= nil then
		cfg.status = cfg.status or {}
		cfg.status.nameStrata = GF.NormalizeFrameStrataToken(data.nameStrata)
	end
	if data.nameFrameLevelOffset ~= nil then
		cfg.status = cfg.status or {}
		cfg.status.nameFrameLevelOffset = clampNumber(data.nameFrameLevelOffset, -20, 50, cfg.status.nameFrameLevelOffset or 5)
	end
	if data.healthClassColor ~= nil or data.healthUseCustomColor ~= nil then cfg.health = cfg.health or {} end
	if data.healthUseCustomColor ~= nil then cfg.health.useCustomColor = data.healthUseCustomColor and true or false end
	if data.healthClassColor ~= nil then cfg.health.useClassColor = data.healthClassColor and true or false end
	if cfg.health and cfg.health.useCustomColor and cfg.health.useClassColor then
		if data.healthClassColor then
			cfg.health.useCustomColor = false
		elseif data.healthUseCustomColor then
			cfg.health.useClassColor = false
		else
			sanitizeHealthColorMode(cfg)
		end
	end
	if data.healthColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.color = data.healthColor
	end
	if data.healthTextLeft ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.textLeft = data.healthTextLeft
	end
	if data.healthTextCenter ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.textCenter = data.healthTextCenter
	end
	if data.healthTextRight ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.textRight = data.healthTextRight
	end
	if data.healthTextColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.textColor = data.healthTextColor
	end
	if data.healthTextClassColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.useTextClassColor = data.healthTextClassColor and true or false
	end
	if data.dataBarTextClassColor ~= nil then
		cfg.dataBar = cfg.dataBar or {}
		cfg.dataBar.useTextClassColor = data.dataBarTextClassColor and true or false
	end
	if data.healthDelimiter ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.textDelimiter = data.healthDelimiter
	end
	if data.healthDelimiterSecondary ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.textDelimiterSecondary = data.healthDelimiterSecondary
	end
	if data.healthDelimiterTertiary ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.textDelimiterTertiary = data.healthDelimiterTertiary
	end
	if data.healthShortNumbers ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.useShortNumbers = data.healthShortNumbers and true or false
	end
	if data.healthRoundPercent ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.roundPercent = data.healthRoundPercent and true or false
	end
	if data.healthHidePercent ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.hidePercentSymbol = data.healthHidePercent and true or false
	end
	if data.healthFontSize ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.fontSize = data.healthFontSize
	end
	if data.healthFont ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.font = data.healthFont
	end
	if data.healthFontOutline ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.fontOutline = data.healthFontOutline
	end
	if data.healthTexture ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.texture = data.healthTexture
	end
	if data.healthSmoothFill ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.smoothFill = data.healthSmoothFill and true or false
	end
	if data.healthTempMaxHealthLoss ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.tempMaxHealthLossEnabled = data.healthTempMaxHealthLoss and true or false
	end
	if data.healthBackdropEnabled ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.backdrop = cfg.health.backdrop or {}
		cfg.health.backdrop.enabled = data.healthBackdropEnabled and true or false
	end
	if data.healthBackdropClampToFill ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.backdrop = cfg.health.backdrop or {}
		cfg.health.backdrop.clampToFill = data.healthBackdropClampToFill and true or false
	end
	if data.healthBackdropColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.backdrop = cfg.health.backdrop or {}
		cfg.health.backdrop.color = data.healthBackdropColor
	end
	if data.healthBackdropTexture ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.backdrop = cfg.health.backdrop or {}
		cfg.health.backdrop.texture = data.healthBackdropTexture
	end
	if data.healthLeftX ~= nil or data.healthLeftY ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.offsetLeft = cfg.health.offsetLeft or {}
		if data.healthLeftX ~= nil then cfg.health.offsetLeft.x = data.healthLeftX end
		if data.healthLeftY ~= nil then cfg.health.offsetLeft.y = data.healthLeftY end
	end
	if data.healthCenterX ~= nil or data.healthCenterY ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.offsetCenter = cfg.health.offsetCenter or {}
		if data.healthCenterX ~= nil then cfg.health.offsetCenter.x = data.healthCenterX end
		if data.healthCenterY ~= nil then cfg.health.offsetCenter.y = data.healthCenterY end
	end
	if data.healthRightX ~= nil or data.healthRightY ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.offsetRight = cfg.health.offsetRight or {}
		if data.healthRightX ~= nil then cfg.health.offsetRight.x = data.healthRightX end
		if data.healthRightY ~= nil then cfg.health.offsetRight.y = data.healthRightY end
	end
	if data.absorbEnabled ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.absorbEnabled = data.absorbEnabled and true or false
	end
	if data.incomingHealEnabled ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.incomingHealEnabled = data.incomingHealEnabled and true or false
	end
	if data.incomingHealSample ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.showSampleIncomingHeal = data.incomingHealSample and true or false
	end
	if data.absorbSample ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.showSampleAbsorb = data.absorbSample and true or false
	end
	if data.absorbTexture ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.absorbTexture = data.absorbTexture
	end
	if data.absorbReverse ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.absorbReverseFill = data.absorbReverse and true or false
		if not cfg.health.absorbReverseFill then cfg.health.absorbDontOverflowHealthBar = false end
	end
	if data.absorbDontOverflow ~= nil then
		cfg.health = cfg.health or {}
		local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
		local reverseAbsorb = cfg.health.absorbReverseFill
		if reverseAbsorb == nil then reverseAbsorb = defH.absorbReverseFill == true end
		cfg.health.absorbDontOverflowHealthBar = (data.absorbDontOverflow and reverseAbsorb == true) and true or false
	end
	if data.absorbOverlayHeight ~= nil then
		cfg.health = cfg.health or {}
		local fallback = GF._computeOverlayHeightFallback(cfg.height, cfg.powerHeight)
		cfg.health.absorbOverlayHeight = GF._resolveOverlayHeightSetting(clampNumber(data.absorbOverlayHeight, 0, 300, 0), fallback)
	end
	if data.absorbOverlayAnchorTop ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.absorbOverlayAnchorTop = data.absorbOverlayAnchorTop and true or false
	end
	if data.absorbUseCustomColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.absorbUseCustomColor = data.absorbUseCustomColor and true or false
	end
	if data.absorbColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.absorbColor = data.absorbColor
	end
	if data.absorbGlow ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.useAbsorbGlow = data.absorbGlow and true or false
	end
	if data.healAbsorbEnabled ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.healAbsorbEnabled = data.healAbsorbEnabled and true or false
	end
	if data.healAbsorbSample ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.showSampleHealAbsorb = data.healAbsorbSample and true or false
	end
	if data.healAbsorbTexture ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.healAbsorbTexture = data.healAbsorbTexture
	end
	if data.healAbsorbReverse ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.healAbsorbReverseFill = data.healAbsorbReverse and true or false
		if cfg.health.healAbsorbReverseFill then cfg.health.healAbsorbDontOverflowHealthBar = false end
	end
	if data.healAbsorbDontOverflow ~= nil then
		cfg.health = cfg.health or {}
		local defH = (DEFAULTS[kind] and DEFAULTS[kind].health) or {}
		local reverseHealAbsorb = cfg.health.healAbsorbReverseFill
		if reverseHealAbsorb == nil then reverseHealAbsorb = defH.healAbsorbReverseFill == true end
		cfg.health.healAbsorbDontOverflowHealthBar = (data.healAbsorbDontOverflow and reverseHealAbsorb ~= true) and true or false
	end
	if data.healAbsorbOverlayHeight ~= nil then
		cfg.health = cfg.health or {}
		local fallback = GF._computeOverlayHeightFallback(cfg.height, cfg.powerHeight)
		cfg.health.healAbsorbOverlayHeight = GF._resolveOverlayHeightSetting(clampNumber(data.healAbsorbOverlayHeight, 0, 300, 0), fallback)
	end
	if data.healAbsorbOverlayAnchorTop ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.healAbsorbOverlayAnchorTop = data.healAbsorbOverlayAnchorTop and true or false
	end
	if data.healAbsorbUseCustomColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.healAbsorbUseCustomColor = data.healAbsorbUseCustomColor and true or false
	end
	if data.healAbsorbColor ~= nil then
		cfg.health = cfg.health or {}
		cfg.health.healAbsorbColor = data.healAbsorbColor
	end
	if
		data.nameColorMode ~= nil
		or data.nameColor ~= nil
		or data.nameStrata ~= nil
		or data.nameFrameLevelOffset ~= nil
		or data.levelEnabled ~= nil
		or data.levelColorMode ~= nil
		or data.levelColor ~= nil
		or data.levelStrata ~= nil
		or data.levelFrameLevelOffset ~= nil
		or data.hideLevelAtMax ~= nil
		or data.levelClassColor ~= nil
		or data.statusTextEnabled ~= nil
		or data.statusTextColor ~= nil
		or data.statusTextFontSize ~= nil
		or data.statusTextFont ~= nil
		or data.statusTextFontOutline ~= nil
		or data.statusTextAnchor ~= nil
		or data.statusTextOffsetX ~= nil
		or data.statusTextOffsetY ~= nil
		or data.statusTextShowGroup ~= nil
		or data.statusTextGroupFormat ~= nil
		or data.groupNumberColor ~= nil
		or data.groupNumberFontSize ~= nil
		or data.groupNumberFont ~= nil
		or data.groupNumberFontOutline ~= nil
		or data.groupNumberAnchor ~= nil
		or data.groupNumberOffsetX ~= nil
		or data.groupNumberOffsetY ~= nil
		or data.rangeFadeEnabled ~= nil
		or data.rangeFadeAlpha ~= nil
		or data.rangeFadeOfflineAlpha ~= nil
		or data.dispelTintEnabled ~= nil
		or data.dispelTintBlizzardPrivateAuraDispels ~= nil
		or data.dispelTintBlizzardDispelMode ~= nil
		or data.dispelTintAlpha ~= nil
		or data.dispelTintFillEnabled ~= nil
		or data.dispelTintFillAlpha ~= nil
		or data.dispelTintFillColor ~= nil
		or data.dispelTintStrata ~= nil
		or data.dispelTintFrameLevelOffset ~= nil
		or data.dispelTintSample ~= nil
		or data.dispelTintGlowEnabled ~= nil
		or data.dispelTintGlowOverOverlay ~= nil
		or data.dispelTintGlowColorMode ~= nil
		or data.dispelTintGlowColor ~= nil
		or data.dispelTintGlowEffect ~= nil
		or data.dispelTintGlowFrequency ~= nil
		or data.dispelTintGlowX ~= nil
		or data.dispelTintGlowY ~= nil
		or data.dispelTintGlowLines ~= nil
		or data.dispelTintGlowThickness ~= nil
	then
		cfg.status = cfg.status or {}
	end
	if data.nameColorMode ~= nil and data.nameClassColor == nil then
		cfg.status.nameColorMode = data.nameColorMode
		cfg.text = cfg.text or {}
		cfg.text.useClassColor = data.nameColorMode == "CLASS"
	end
	if data.nameColor ~= nil then cfg.status.nameColor = data.nameColor end
	if data.levelEnabled ~= nil then cfg.status.levelEnabled = data.levelEnabled and true or false end
	if data.hideLevelAtMax ~= nil then cfg.status.hideLevelAtMax = data.hideLevelAtMax and true or false end
	if data.levelClassColor ~= nil then cfg.status.levelColorMode = data.levelClassColor and "CLASS" or "CUSTOM" end
	if data.levelColorMode ~= nil then cfg.status.levelColorMode = data.levelColorMode end
	if data.levelColor ~= nil then cfg.status.levelColor = data.levelColor end
	if data.levelFontSize ~= nil then cfg.status.levelFontSize = data.levelFontSize end
	if data.levelFont ~= nil then cfg.status.levelFont = (type(data.levelFont) == "string" and data.levelFont ~= "") and data.levelFont or GF.GlobalFontConfigKey() end
	if data.levelFontOutline ~= nil then cfg.status.levelFontOutline = data.levelFontOutline end
	if data.levelAnchor ~= nil then cfg.status.levelAnchor = data.levelAnchor end
	if data.levelStrata ~= nil then cfg.status.levelStrata = GF.NormalizeFrameStrataToken(data.levelStrata) end
	if data.levelFrameLevelOffset ~= nil then cfg.status.levelFrameLevelOffset = clampNumber(data.levelFrameLevelOffset, -20, 50, cfg.status.levelFrameLevelOffset or 5) end
	if data.levelOffsetX ~= nil or data.levelOffsetY ~= nil then
		cfg.status.levelOffset = cfg.status.levelOffset or {}
		if data.levelOffsetX ~= nil then cfg.status.levelOffset.x = data.levelOffsetX end
		if data.levelOffsetY ~= nil then cfg.status.levelOffset.y = data.levelOffsetY end
	end
	if
		data.statusTextEnabled ~= nil
		or data.statusTextColor ~= nil
		or data.statusTextFontSize ~= nil
		or data.statusTextFont ~= nil
		or data.statusTextFontOutline ~= nil
		or data.statusTextAnchor ~= nil
		or data.statusTextOffsetX ~= nil
		or data.statusTextOffsetY ~= nil
		or data.statusTextShowOffline ~= nil
		or data.statusTextShowAFK ~= nil
		or data.statusTextShowDead ~= nil
		or data.statusTextShowDND ~= nil
		or data.statusTextShowGhost ~= nil
		or data.statusTextShowGroup ~= nil
		or data.statusTextGroupFormat ~= nil
		or data.statusTextHideHealthTextOffline ~= nil
	then
		cfg.status.unitStatus = cfg.status.unitStatus or {}
		if data.statusTextEnabled ~= nil then cfg.status.unitStatus.enabled = data.statusTextEnabled and true or false end
		if data.statusTextColor ~= nil then cfg.status.unitStatus.color = data.statusTextColor end
		if data.statusTextFontSize ~= nil then cfg.status.unitStatus.fontSize = data.statusTextFontSize end
		if data.statusTextFont ~= nil then cfg.status.unitStatus.font = data.statusTextFont end
		if data.statusTextFontOutline ~= nil then cfg.status.unitStatus.fontOutline = data.statusTextFontOutline end
		if data.statusTextAnchor ~= nil then cfg.status.unitStatus.anchor = data.statusTextAnchor end
		if data.statusTextOffsetX ~= nil or data.statusTextOffsetY ~= nil then
			cfg.status.unitStatus.offset = cfg.status.unitStatus.offset or {}
			if data.statusTextOffsetX ~= nil then cfg.status.unitStatus.offset.x = data.statusTextOffsetX end
			if data.statusTextOffsetY ~= nil then cfg.status.unitStatus.offset.y = data.statusTextOffsetY end
		end
		if data.statusTextShowOffline ~= nil then cfg.status.unitStatus.showOffline = data.statusTextShowOffline and true or false end
		if data.statusTextShowAFK ~= nil then cfg.status.unitStatus.showAFK = data.statusTextShowAFK and true or false end
		if data.statusTextShowDead ~= nil then cfg.status.unitStatus.showDead = data.statusTextShowDead and true or false end
		if data.statusTextShowDND ~= nil then cfg.status.unitStatus.showDND = data.statusTextShowDND and true or false end
		if data.statusTextShowGhost ~= nil then cfg.status.unitStatus.showGhost = data.statusTextShowGhost and true or false end
		if data.statusTextShowGroup ~= nil then cfg.status.unitStatus.showGroup = data.statusTextShowGroup and true or false end
		if data.statusTextGroupFormat ~= nil then cfg.status.unitStatus.groupFormat = data.statusTextGroupFormat end
		if data.statusTextHideHealthTextOffline ~= nil then cfg.status.unitStatus.hideHealthTextWhenOffline = data.statusTextHideHealthTextOffline and true or false end
	end
	if
		data.statusTextShowGroup ~= nil
		or data.statusTextGroupFormat ~= nil
		or data.groupNumberColor ~= nil
		or data.groupNumberFontSize ~= nil
		or data.groupNumberFont ~= nil
		or data.groupNumberFontOutline ~= nil
		or data.groupNumberAnchor ~= nil
		or data.groupNumberOffsetX ~= nil
		or data.groupNumberOffsetY ~= nil
	then
		cfg.status.groupNumber = cfg.status.groupNumber or {}
		if data.statusTextShowGroup ~= nil then cfg.status.groupNumber.enabled = data.statusTextShowGroup and true or false end
		if data.statusTextGroupFormat ~= nil then cfg.status.groupNumber.format = normalizeGroupNumberFormat(data.statusTextGroupFormat) or "GROUP" end
		if data.groupNumberColor ~= nil then cfg.status.groupNumber.color = data.groupNumberColor end
		if data.groupNumberFontSize ~= nil then cfg.status.groupNumber.fontSize = data.groupNumberFontSize end
		if data.groupNumberFont ~= nil then cfg.status.groupNumber.font = data.groupNumberFont end
		if data.groupNumberFontOutline ~= nil then cfg.status.groupNumber.fontOutline = data.groupNumberFontOutline end
		if data.groupNumberAnchor ~= nil then cfg.status.groupNumber.anchor = data.groupNumberAnchor end
		if data.groupNumberOffsetX ~= nil or data.groupNumberOffsetY ~= nil then
			cfg.status.groupNumber.offset = cfg.status.groupNumber.offset or {}
			if data.groupNumberOffsetX ~= nil then cfg.status.groupNumber.offset.x = data.groupNumberOffsetX end
			if data.groupNumberOffsetY ~= nil then cfg.status.groupNumber.offset.y = data.groupNumberOffsetY end
		end
	end
	if data.rangeFadeEnabled ~= nil or data.rangeFadeAlpha ~= nil or data.rangeFadeOfflineAlpha ~= nil then
		cfg.status = cfg.status or {}
		cfg.status.rangeFade = cfg.status.rangeFade or {}
		if data.rangeFadeEnabled ~= nil then cfg.status.rangeFade.enabled = data.rangeFadeEnabled and true or false end
		if data.rangeFadeAlpha ~= nil then cfg.status.rangeFade.alpha = clampNumber(data.rangeFadeAlpha, 0, 1, cfg.status.rangeFade.alpha or 0.55) end
		if data.rangeFadeOfflineAlpha ~= nil then cfg.status.rangeFade.offlineAlpha = clampNumber(data.rangeFadeOfflineAlpha, 0, 1, cfg.status.rangeFade.offlineAlpha or 0.4) end
	end
	if
		data.groupIndicatorEnabled ~= nil
		or data.groupIndicatorHidePerFrame ~= nil
		or data.groupIndicatorFormat ~= nil
		or data.groupIndicatorColor ~= nil
		or data.groupIndicatorFontSize ~= nil
		or data.groupIndicatorFont ~= nil
		or data.groupIndicatorFontOutline ~= nil
		or data.groupIndicatorAnchor ~= nil
		or data.groupIndicatorOffsetX ~= nil
		or data.groupIndicatorOffsetY ~= nil
	then
		cfg.groupIndicator = cfg.groupIndicator or {}
		if data.groupIndicatorEnabled ~= nil then cfg.groupIndicator.enabled = data.groupIndicatorEnabled and true or false end
		if data.groupIndicatorHidePerFrame ~= nil then cfg.groupIndicator.hidePerFrame = data.groupIndicatorHidePerFrame and true or false end
		if data.groupIndicatorFormat ~= nil then cfg.groupIndicator.format = normalizeGroupNumberFormat(data.groupIndicatorFormat) or "GROUP" end
		if data.groupIndicatorColor ~= nil then cfg.groupIndicator.color = data.groupIndicatorColor end
		if data.groupIndicatorFontSize ~= nil then cfg.groupIndicator.fontSize = data.groupIndicatorFontSize end
		if data.groupIndicatorFont ~= nil then cfg.groupIndicator.font = data.groupIndicatorFont end
		if data.groupIndicatorFontOutline ~= nil then cfg.groupIndicator.fontOutline = data.groupIndicatorFontOutline end
		if data.groupIndicatorAnchor ~= nil then cfg.groupIndicator.anchor = data.groupIndicatorAnchor end
		if data.groupIndicatorOffsetX ~= nil or data.groupIndicatorOffsetY ~= nil then
			cfg.groupIndicator.offset = cfg.groupIndicator.offset or {}
			if data.groupIndicatorOffsetX ~= nil then cfg.groupIndicator.offset.x = data.groupIndicatorOffsetX end
			if data.groupIndicatorOffsetY ~= nil then cfg.groupIndicator.offset.y = data.groupIndicatorOffsetY end
		end
	end
	if
		data.dispelTintEnabled ~= nil
		or data.dispelTintBlizzardPrivateAuraDispels ~= nil
		or data.dispelTintBlizzardDispelMode ~= nil
		or data.dispelTintAlpha ~= nil
		or data.dispelTintFillEnabled ~= nil
		or data.dispelTintFillAlpha ~= nil
		or data.dispelTintFillColor ~= nil
		or data.dispelTintStrata ~= nil
		or data.dispelTintFrameLevelOffset ~= nil
		or data.dispelTintSample ~= nil
		or data.dispelTintGlowEnabled ~= nil
		or data.dispelTintGlowOverOverlay ~= nil
		or data.dispelTintGlowColorMode ~= nil
		or data.dispelTintGlowColor ~= nil
		or data.dispelTintGlowEffect ~= nil
		or data.dispelTintGlowFrequency ~= nil
		or data.dispelTintGlowX ~= nil
		or data.dispelTintGlowY ~= nil
		or data.dispelTintGlowLines ~= nil
		or data.dispelTintGlowThickness ~= nil
	then
		cfg.status.dispelTint = cfg.status.dispelTint or {}
		if data.dispelTintEnabled ~= nil then cfg.status.dispelTint.enabled = data.dispelTintEnabled and true or false end
		if data.dispelTintBlizzardPrivateAuraDispels ~= nil then cfg.status.dispelTint.blizzardPrivateAuraDispels = data.dispelTintBlizzardPrivateAuraDispels and true or false end
		if data.dispelTintBlizzardDispelMode ~= nil then cfg.status.dispelTint.blizzardDispelIndicatorMode = GF.NormalizeBlizzardDispelIndicatorMode(data.dispelTintBlizzardDispelMode) or GF.BLIZZARD_DISPEL_MODE_ALL end
		if data.dispelTintAlpha ~= nil then cfg.status.dispelTint.alpha = clampNumber(data.dispelTintAlpha, 0, 1, cfg.status.dispelTint.alpha or 0.25) end
		if data.dispelTintFillEnabled ~= nil then cfg.status.dispelTint.fillEnabled = data.dispelTintFillEnabled and true or false end
		if data.dispelTintFillAlpha ~= nil then cfg.status.dispelTint.fillAlpha = clampNumber(data.dispelTintFillAlpha, 0, 1, cfg.status.dispelTint.fillAlpha or 0.2) end
		if data.dispelTintFillColor ~= nil then cfg.status.dispelTint.fillColor = data.dispelTintFillColor end
		if data.dispelTintStrata ~= nil then cfg.status.dispelTint.strata = GF.NormalizeFrameStrataToken(data.dispelTintStrata) end
		if data.dispelTintFrameLevelOffset ~= nil then
			local levelOffset = clampNumber(data.dispelTintFrameLevelOffset, -20, 1000, cfg.status.dispelTint.frameLevelOffset or 1)
			levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
			cfg.status.dispelTint.frameLevelOffset = levelOffset
		end
		if data.dispelTintSample ~= nil then cfg.status.dispelTint.showSample = data.dispelTintSample and true or false end
		if data.dispelTintGlowEnabled ~= nil then cfg.status.dispelTint.glowEnabled = data.dispelTintGlowEnabled and true or false end
		if data.dispelTintGlowOverOverlay ~= nil then cfg.status.dispelTint.glowOverOverlay = data.dispelTintGlowOverOverlay and true or false end
		if data.dispelTintGlowColorMode ~= nil then cfg.status.dispelTint.glowColorMode = data.dispelTintGlowColorMode end
		if data.dispelTintGlowColor ~= nil then cfg.status.dispelTint.glowColor = data.dispelTintGlowColor end
		if data.dispelTintGlowEffect ~= nil then cfg.status.dispelTint.glowEffect = data.dispelTintGlowEffect end
		if data.dispelTintGlowFrequency ~= nil then cfg.status.dispelTint.glowFrequency = clampNumber(data.dispelTintGlowFrequency, -1.5, 1.5, cfg.status.dispelTint.glowFrequency or 0.25) end
		if data.dispelTintGlowX ~= nil then cfg.status.dispelTint.glowX = clampNumber(data.dispelTintGlowX, -10, 10, cfg.status.dispelTint.glowX or 0) end
		if data.dispelTintGlowY ~= nil then cfg.status.dispelTint.glowY = clampNumber(data.dispelTintGlowY, -10, 10, cfg.status.dispelTint.glowY or 0) end
		if data.dispelTintGlowLines ~= nil then cfg.status.dispelTint.glowLines = clampNumber(data.dispelTintGlowLines, 1, 20, cfg.status.dispelTint.glowLines or 8) end
		if data.dispelTintGlowThickness ~= nil then cfg.status.dispelTint.glowThickness = clampNumber(data.dispelTintGlowThickness, 1, 10, cfg.status.dispelTint.glowThickness or 3) end
	end
	if data.raidIconEnabled ~= nil or data.raidIconSize ~= nil or data.raidIconPoint ~= nil or data.raidIconOffsetX ~= nil or data.raidIconOffsetY ~= nil then
		cfg.status.raidIcon = cfg.status.raidIcon or {}
		if data.raidIconEnabled ~= nil then cfg.status.raidIcon.enabled = data.raidIconEnabled and true or false end
		if data.raidIconSize ~= nil then cfg.status.raidIcon.size = data.raidIconSize end
		if data.raidIconPoint ~= nil then
			cfg.status.raidIcon.point = data.raidIconPoint
			cfg.status.raidIcon.relativePoint = data.raidIconPoint
		end
		if data.raidIconOffsetX ~= nil then cfg.status.raidIcon.x = data.raidIconOffsetX end
		if data.raidIconOffsetY ~= nil then cfg.status.raidIcon.y = data.raidIconOffsetY end
	end
	if
		data.raidAssignmentIconEnabled ~= nil
		or data.raidAssignmentIconSize ~= nil
		or data.raidAssignmentIconPoint ~= nil
		or data.raidAssignmentIconOffsetX ~= nil
		or data.raidAssignmentIconOffsetY ~= nil
	then
		cfg.status.raidAssignmentIcon = cfg.status.raidAssignmentIcon or {}
		if data.raidAssignmentIconEnabled ~= nil then cfg.status.raidAssignmentIcon.enabled = data.raidAssignmentIconEnabled and true or false end
		if data.raidAssignmentIconSize ~= nil then cfg.status.raidAssignmentIcon.size = data.raidAssignmentIconSize end
		if data.raidAssignmentIconPoint ~= nil then
			cfg.status.raidAssignmentIcon.point = data.raidAssignmentIconPoint
			cfg.status.raidAssignmentIcon.relativePoint = data.raidAssignmentIconPoint
		end
		if data.raidAssignmentIconOffsetX ~= nil then cfg.status.raidAssignmentIcon.x = data.raidAssignmentIconOffsetX end
		if data.raidAssignmentIconOffsetY ~= nil then cfg.status.raidAssignmentIcon.y = data.raidAssignmentIconOffsetY end
	end
	if data.leaderIconEnabled ~= nil or data.leaderIconSize ~= nil or data.leaderIconPoint ~= nil or data.leaderIconOffsetX ~= nil or data.leaderIconOffsetY ~= nil then
		cfg.status.leaderIcon = cfg.status.leaderIcon or {}
		if data.leaderIconEnabled ~= nil then cfg.status.leaderIcon.enabled = data.leaderIconEnabled and true or false end
		if data.leaderIconSize ~= nil then cfg.status.leaderIcon.size = data.leaderIconSize end
		if data.leaderIconPoint ~= nil then
			cfg.status.leaderIcon.point = data.leaderIconPoint
			cfg.status.leaderIcon.relativePoint = data.leaderIconPoint
		end
		if data.leaderIconOffsetX ~= nil then cfg.status.leaderIcon.x = data.leaderIconOffsetX end
		if data.leaderIconOffsetY ~= nil then cfg.status.leaderIcon.y = data.leaderIconOffsetY end
	end
	if data.assistIconEnabled ~= nil or data.assistIconSize ~= nil or data.assistIconPoint ~= nil or data.assistIconOffsetX ~= nil or data.assistIconOffsetY ~= nil then
		cfg.status.assistIcon = cfg.status.assistIcon or {}
		if data.assistIconEnabled ~= nil then cfg.status.assistIcon.enabled = data.assistIconEnabled and true or false end
		if data.assistIconSize ~= nil then cfg.status.assistIcon.size = data.assistIconSize end
		if data.assistIconPoint ~= nil then
			cfg.status.assistIcon.point = data.assistIconPoint
			cfg.status.assistIcon.relativePoint = data.assistIconPoint
		end
		if data.assistIconOffsetX ~= nil then cfg.status.assistIcon.x = data.assistIconOffsetX end
		if data.assistIconOffsetY ~= nil then cfg.status.assistIcon.y = data.assistIconOffsetY end
	end
	for _, meta in ipairs(GFH.STATUS_ICON_EDITMODE_META or EMPTY) do
		local iconKey = meta and meta.key
		if iconKey then
			local enabled = data[iconKey .. "Enabled"]
			local sample = data[iconKey .. "Sample"]
			local size = data[iconKey .. "Size"]
			local point = data[iconKey .. "Point"]
			local relativePoint = data[iconKey .. "RelativePoint"]
			local offsetX = data[iconKey .. "OffsetX"]
			local offsetY = data[iconKey .. "OffsetY"]
			if enabled ~= nil or sample ~= nil or size ~= nil or point ~= nil or relativePoint ~= nil or offsetX ~= nil or offsetY ~= nil then
				cfg.status[iconKey] = cfg.status[iconKey] or {}
				local iconCfg = cfg.status[iconKey]
				local defIconCfg = DEFAULTS[kind] and DEFAULTS[kind].status and DEFAULTS[kind].status[iconKey] or EMPTY
				local defaultSize = defIconCfg.size or 16
				if enabled ~= nil then iconCfg.enabled = enabled and true or false end
				if sample ~= nil then iconCfg.sample = sample and true or false end
				if size ~= nil then iconCfg.size = clampNumber(size, 8, 40, iconCfg.size or defaultSize) end
				if point ~= nil then
					iconCfg.point = point
					if relativePoint == nil then iconCfg.relativePoint = point end
				end
				if relativePoint ~= nil then iconCfg.relativePoint = relativePoint end
				if offsetX ~= nil then iconCfg.x = clampNumber(offsetX, -200, 200, iconCfg.x or 0) end
				if offsetY ~= nil then iconCfg.y = clampNumber(offsetY, -200, 200, iconCfg.y or 0) end
			end
		end
	end
	if data.roleIconEnabled ~= nil then
		cfg.roleIcon = cfg.roleIcon or {}
		cfg.roleIcon.enabled = data.roleIconEnabled and true or false
	end
	if data.roleIconSize ~= nil then
		cfg.roleIcon = cfg.roleIcon or {}
		cfg.roleIcon.size = data.roleIconSize
	end
	if data.roleIconPoint ~= nil then
		cfg.roleIcon = cfg.roleIcon or {}
		cfg.roleIcon.point = data.roleIconPoint
		cfg.roleIcon.relativePoint = data.roleIconPoint
	end
	if data.roleIconOffsetX ~= nil or data.roleIconOffsetY ~= nil then
		cfg.roleIcon = cfg.roleIcon or {}
		if data.roleIconOffsetX ~= nil then cfg.roleIcon.x = data.roleIconOffsetX end
		if data.roleIconOffsetY ~= nil then cfg.roleIcon.y = data.roleIconOffsetY end
	end
	if data.roleIconStyle ~= nil then
		cfg.roleIcon = cfg.roleIcon or {}
		cfg.roleIcon.style = data.roleIconStyle
	end
	if data.roleIconRoles ~= nil then
		cfg.roleIcon = cfg.roleIcon or {}
		cfg.roleIcon.showRoles = GF._sharedEdit.csm(data.roleIconRoles)
	end
	if data.powerRoles ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.showRoles = GF._sharedEdit.csm(data.powerRoles)
	end
	if data.powerSpecs ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.showSpecs = GF._sharedEdit.csm(data.powerSpecs)
	end
	if data.powerDetached ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detached = data.powerDetached and true or false
	end
	if data.powerDetachedMatchHealthWidth ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedMatchHealthWidth = data.powerDetachedMatchHealthWidth and true or false
	end
	if data.powerDetachedWidth ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.width = clampNumber(data.powerDetachedWidth, 10, 600, cfg.power.width or cfg.width or 100)
	end
	if data.powerDetachedHeight ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedHeight = clampNumber(data.powerDetachedHeight, 1, 1000, cfg.power.detachedHeight or cfg.powerHeight or 6)
	end
	if data.powerDetachedBorder ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedBorder = data.powerDetachedBorder and true or false
	end
	if data.powerDetachedBorderTexture ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedBorderTexture = data.powerDetachedBorderTexture or "DEFAULT"
	end
	if data.powerDetachedBorderSize ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedBorderSize = clampNumber(data.powerDetachedBorderSize, 1, 64, cfg.power.detachedBorderSize or 1)
	end
	if data.powerDetachedBorderOffset ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedBorderOffset = clampNumber(data.powerDetachedBorderOffset, 0, 100, cfg.power.detachedBorderOffset or 1)
	end
	if data.powerDetachedStrata ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedStrata = GF.NormalizeFrameStrataToken(data.powerDetachedStrata)
	end
	if data.powerDetachedFrameLevelOffset ~= nil then
		cfg.power = cfg.power or {}
		local levelOffset = clampNumber(data.powerDetachedFrameLevelOffset, -20, 1000, cfg.power.detachedFrameLevelOffset or 2)
		levelOffset = floor(levelOffset + (levelOffset >= 0 and 0.5 or -0.5))
		cfg.power.detachedFrameLevelOffset = levelOffset
	end
	if data.powerDetachedGrowFromCenter ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.detachedGrowFromCenter = data.powerDetachedGrowFromCenter and true or false
	end
	if data.powerDetachedOffsetX ~= nil or data.powerDetachedOffsetY ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.offset = cfg.power.offset or {}
		if data.powerDetachedOffsetX ~= nil then cfg.power.offset.x = clampNumber(data.powerDetachedOffsetX, -400, 400, cfg.power.offset.x or 0) end
		if data.powerDetachedOffsetY ~= nil then cfg.power.offset.y = clampNumber(data.powerDetachedOffsetY, -400, 400, cfg.power.offset.y or 0) end
	end
	if data.powerTextLeft ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.textLeft = data.powerTextLeft
	end
	if data.powerTextCenter ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.textCenter = data.powerTextCenter
	end
	if data.powerTextRight ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.textRight = data.powerTextRight
	end
	if data.powerDelimiter ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.textDelimiter = data.powerDelimiter
	end
	if data.powerDelimiterSecondary ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.textDelimiterSecondary = data.powerDelimiterSecondary
	end
	if data.powerDelimiterTertiary ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.textDelimiterTertiary = data.powerDelimiterTertiary
	end
	if data.powerShortNumbers ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.useShortNumbers = data.powerShortNumbers and true or false
	end
	if data.powerRoundPercent ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.roundPercent = data.powerRoundPercent and true or false
	end
	if data.powerHidePercent ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.hidePercentSymbol = data.powerHidePercent and true or false
	end
	if data.powerFontSize ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.fontSize = data.powerFontSize
	end
	if data.powerFont ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.font = data.powerFont
	end
	if data.powerFontOutline ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.fontOutline = data.powerFontOutline
	end
	if data.powerTexture ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.texture = data.powerTexture
	end
	if data.powerSmoothFill ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.smoothFill = data.powerSmoothFill and true or false
	end
	if data.powerBackdropEnabled ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.backdrop = cfg.power.backdrop or {}
		cfg.power.backdrop.enabled = data.powerBackdropEnabled and true or false
	end
	if data.powerBackdropColor ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.backdrop = cfg.power.backdrop or {}
		cfg.power.backdrop.color = data.powerBackdropColor
	end
	if data.powerBackdropTexture ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.backdrop = cfg.power.backdrop or {}
		cfg.power.backdrop.texture = data.powerBackdropTexture
	end
	if data.powerLeftX ~= nil or data.powerLeftY ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.offsetLeft = cfg.power.offsetLeft or {}
		if data.powerLeftX ~= nil then cfg.power.offsetLeft.x = data.powerLeftX end
		if data.powerLeftY ~= nil then cfg.power.offsetLeft.y = data.powerLeftY end
	end
	if data.powerCenterX ~= nil or data.powerCenterY ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.offsetCenter = cfg.power.offsetCenter or {}
		if data.powerCenterX ~= nil then cfg.power.offsetCenter.x = data.powerCenterX end
		if data.powerCenterY ~= nil then cfg.power.offsetCenter.y = data.powerCenterY end
	end
	if data.powerRightX ~= nil or data.powerRightY ~= nil then
		cfg.power = cfg.power or {}
		cfg.power.offsetRight = cfg.power.offsetRight or {}
		if data.powerRightX ~= nil then cfg.power.offsetRight.x = data.powerRightX end
		if data.powerRightY ~= nil then cfg.power.offsetRight.y = data.powerRightY end
	end

	local ac = ensureAuraConfig(cfg)
	if data.auraRenderer ~= nil then ac.renderer = (data.auraRenderer == "BLIZZARD") and "BLIZZARD" or "CUSTOM" end
	if data.auraRendererBlizzardTypes ~= nil then ac.blizzardTypes = GF._sharedEdit.csm(data.auraRendererBlizzardTypes) end
	if data.auraRendererBlizzardIconSize ~= nil then ac.blizzardIconSize = clampNumber(data.auraRendererBlizzardIconSize, 8, 120, ac.blizzardIconSize or 16) end
	if data.auraRendererBlizzardCooldownText ~= nil then ac.blizzardShowCooldownText = data.auraRendererBlizzardCooldownText and true or false end
	if data.auraRendererBlizzardOrganization ~= nil then ac.blizzardOrganizationType = GF.NormalizeBlizzardAuraOrganization(data.auraRendererBlizzardOrganization) or GF.BLIZZARD_AURA_ORGANIZATION_LEGACY end
	if data.auraRendererBlizzardDispelMode ~= nil then ac.blizzardDispelIndicatorMode = GF.NormalizeBlizzardDispelIndicatorMode(data.auraRendererBlizzardDispelMode) or GF.BLIZZARD_DISPEL_MODE_ALL end
	if data.buffsEnabled ~= nil then ac.buff.enabled = data.buffsEnabled and true or false end
	if data.buffAnchor ~= nil then ac.buff.anchorPoint = data.buffAnchor end
	if data.buffAnchorOutside ~= nil then ac.buff.anchorOutside = data.buffAnchorOutside and true or false end
	if data.buffGrowth ~= nil then
		applyAuraGrowth(ac.buff, data.buffGrowth)
	elseif data.buffGrowthX ~= nil or data.buffGrowthY ~= nil then
		if data.buffGrowthX ~= nil then ac.buff.growthX = data.buffGrowthX end
		if data.buffGrowthY ~= nil then ac.buff.growthY = data.buffGrowthY end
	end
	if data.buffOffsetX ~= nil then ac.buff.x = data.buffOffsetX end
	if data.buffOffsetY ~= nil then ac.buff.y = data.buffOffsetY end
	if data.buffSize ~= nil then ac.buff.size = data.buffSize end
	if data.buffIconShape ~= nil then
		ac.buff.iconShape = GF.NormalizeAuraIconShape(data.buffIconShape, ac.buff.iconShape or "DEFAULT")
		ac.buff.borderTexture = GF.NormalizeAuraBorderTexture(ac.buff.borderTexture, "DEFAULT", ac.buff.iconShape)
	end
	if data.buffPerRow ~= nil then ac.buff.perRow = data.buffPerRow end
	if data.buffMax ~= nil then ac.buff.max = GF.ClampAuraCount(data.buffMax, ac.buff.max or 6) or ac.buff.max or 6 end
	if data.buffSpacing ~= nil then ac.buff.spacing = data.buffSpacing end
	if data.buffBorderTexture ~= nil then ac.buff.borderTexture = GF.NormalizeAuraBorderTexture(data.buffBorderTexture, ac.buff.borderTexture or "DEFAULT", ac.buff.iconShape) end
	if data.buffBorderSize ~= nil then ac.buff.borderSize = clampNumber(data.buffBorderSize, 1, 64, ac.buff.borderSize or 2) end
	if data.buffBorderOffset ~= nil then ac.buff.borderOffset = clampNumber(data.buffBorderOffset, -64, 64, ac.buff.borderOffset or 0) end
	if data.buffFilters ~= nil then
		ac.buff.filterSelection = GF._sharedEdit.csm(data.buffFilters)
	elseif data.buffHelpfulFilterMode ~= nil then
		ac.buff.helpfulFilterMode = GF.NormalizeBuffHelpfulFilterMode(data.buffHelpfulFilterMode)
		ac.buff.filterSelection = GF.ExportGroupBuffSelection(ac.buff)
	end
	if data.buffCooldownEnabled ~= nil then ac.buff.showCooldown = data.buffCooldownEnabled and true or false end
	if data.buffCooldownTextEnabled ~= nil then ac.buff.showCooldownText = data.buffCooldownTextEnabled and true or false end
	if data.buffCooldownTextAnchor ~= nil then ac.buff.cooldownAnchor = data.buffCooldownTextAnchor end
	if data.buffCooldownTextOffsetX ~= nil or data.buffCooldownTextOffsetY ~= nil then
		ac.buff.cooldownOffset = ac.buff.cooldownOffset or {}
		if data.buffCooldownTextOffsetX ~= nil then ac.buff.cooldownOffset.x = data.buffCooldownTextOffsetX end
		if data.buffCooldownTextOffsetY ~= nil then ac.buff.cooldownOffset.y = data.buffCooldownTextOffsetY end
	end
	if data.buffCooldownTextSize ~= nil then ac.buff.cooldownFontSize = data.buffCooldownTextSize end
	if data.buffCooldownTextFont ~= nil then ac.buff.cooldownFont = data.buffCooldownTextFont end
	if data.buffCooldownTextOutline ~= nil then ac.buff.cooldownFontOutline = data.buffCooldownTextOutline end
	if data.buffStackTextEnabled ~= nil then ac.buff.showStacks = data.buffStackTextEnabled and true or false end
	if data.buffStackAnchor ~= nil then ac.buff.countAnchor = data.buffStackAnchor end
	if data.buffStackOffsetX ~= nil or data.buffStackOffsetY ~= nil then
		ac.buff.countOffset = ac.buff.countOffset or {}
		if data.buffStackOffsetX ~= nil then ac.buff.countOffset.x = data.buffStackOffsetX end
		if data.buffStackOffsetY ~= nil then ac.buff.countOffset.y = data.buffStackOffsetY end
	end
	if data.buffStackSize ~= nil then ac.buff.countFontSize = data.buffStackSize end
	if data.buffStackFont ~= nil then ac.buff.countFont = data.buffStackFont end
	if data.buffStackOutline ~= nil then ac.buff.countFontOutline = data.buffStackOutline end

	if data.debuffsEnabled ~= nil then ac.debuff.enabled = data.debuffsEnabled and true or false end
	if data.debuffFilters ~= nil then ac.debuff.filterSelection = GF._sharedEdit.csm(data.debuffFilters) end
	if data.debuffAnchor ~= nil then ac.debuff.anchorPoint = data.debuffAnchor end
	if data.debuffAnchorOutside ~= nil then ac.debuff.anchorOutside = data.debuffAnchorOutside and true or false end
	if data.debuffGrowth ~= nil then
		applyAuraGrowth(ac.debuff, data.debuffGrowth)
	elseif data.debuffGrowthX ~= nil or data.debuffGrowthY ~= nil then
		if data.debuffGrowthX ~= nil then ac.debuff.growthX = data.debuffGrowthX end
		if data.debuffGrowthY ~= nil then ac.debuff.growthY = data.debuffGrowthY end
	end
	if data.debuffOffsetX ~= nil then ac.debuff.x = data.debuffOffsetX end
	if data.debuffOffsetY ~= nil then ac.debuff.y = data.debuffOffsetY end
	if data.debuffSize ~= nil then ac.debuff.size = data.debuffSize end
	if data.debuffIconShape ~= nil then
		ac.debuff.iconShape = GF.NormalizeAuraIconShape(data.debuffIconShape, ac.debuff.iconShape or "DEFAULT")
		ac.debuff.borderTexture = GF.NormalizeAuraBorderTexture(ac.debuff.borderTexture, "DEFAULT", ac.debuff.iconShape)
	end
	if data.debuffPerRow ~= nil then ac.debuff.perRow = data.debuffPerRow end
	if data.debuffMax ~= nil then ac.debuff.max = GF.ClampAuraCount(data.debuffMax, ac.debuff.max or 6) or ac.debuff.max or 6 end
	if data.debuffDisplayLargerRoleSpecific ~= nil then ac.debuff.displayLargerRoleSpecificDebuffs = data.debuffDisplayLargerRoleSpecific and true or false end
	if data.debuffSpacing ~= nil then ac.debuff.spacing = data.debuffSpacing end
	if data.debuffBorderTexture ~= nil then ac.debuff.borderTexture = GF.NormalizeAuraBorderTexture(data.debuffBorderTexture, ac.debuff.borderTexture or "DEFAULT", ac.debuff.iconShape) end
	if data.debuffBorderSize ~= nil then ac.debuff.borderSize = clampNumber(data.debuffBorderSize, 1, 64, ac.debuff.borderSize or 2) end
	if data.debuffBorderOffset ~= nil then ac.debuff.borderOffset = clampNumber(data.debuffBorderOffset, -64, 64, ac.debuff.borderOffset or 0) end
	if data.debuffShowDispelIcon ~= nil then ac.debuff.showDispelIcon = data.debuffShowDispelIcon and true or false end
	if data.debuffCooldownEnabled ~= nil then ac.debuff.showCooldown = data.debuffCooldownEnabled and true or false end
	if data.debuffCooldownTextEnabled ~= nil then ac.debuff.showCooldownText = data.debuffCooldownTextEnabled and true or false end
	if data.debuffCooldownTextAnchor ~= nil then ac.debuff.cooldownAnchor = data.debuffCooldownTextAnchor end
	if data.debuffCooldownTextOffsetX ~= nil or data.debuffCooldownTextOffsetY ~= nil then
		ac.debuff.cooldownOffset = ac.debuff.cooldownOffset or {}
		if data.debuffCooldownTextOffsetX ~= nil then ac.debuff.cooldownOffset.x = data.debuffCooldownTextOffsetX end
		if data.debuffCooldownTextOffsetY ~= nil then ac.debuff.cooldownOffset.y = data.debuffCooldownTextOffsetY end
	end
	if data.debuffCooldownTextSize ~= nil then ac.debuff.cooldownFontSize = data.debuffCooldownTextSize end
	if data.debuffCooldownTextFont ~= nil then ac.debuff.cooldownFont = data.debuffCooldownTextFont end
	if data.debuffCooldownTextOutline ~= nil then ac.debuff.cooldownFontOutline = data.debuffCooldownTextOutline end
	if data.debuffStackTextEnabled ~= nil then ac.debuff.showStacks = data.debuffStackTextEnabled and true or false end
	if data.debuffStackAnchor ~= nil then ac.debuff.countAnchor = data.debuffStackAnchor end
	if data.debuffStackOffsetX ~= nil or data.debuffStackOffsetY ~= nil then
		ac.debuff.countOffset = ac.debuff.countOffset or {}
		if data.debuffStackOffsetX ~= nil then ac.debuff.countOffset.x = data.debuffStackOffsetX end
		if data.debuffStackOffsetY ~= nil then ac.debuff.countOffset.y = data.debuffStackOffsetY end
	end
	if data.debuffStackSize ~= nil then ac.debuff.countFontSize = data.debuffStackSize end
	if data.debuffStackFont ~= nil then ac.debuff.countFont = data.debuffStackFont end
	if data.debuffStackOutline ~= nil then ac.debuff.countFontOutline = data.debuffStackOutline end

	if data.externalsEnabled ~= nil then ac.externals.enabled = data.externalsEnabled and true or false end
	if data.externalAnchor ~= nil then ac.externals.anchorPoint = data.externalAnchor end
	if data.externalAnchorOutside ~= nil then ac.externals.anchorOutside = data.externalAnchorOutside and true or false end
	if data.externalGrowth ~= nil then
		applyAuraGrowth(ac.externals, data.externalGrowth)
	elseif data.externalGrowthX ~= nil or data.externalGrowthY ~= nil then
		if data.externalGrowthX ~= nil then ac.externals.growthX = data.externalGrowthX end
		if data.externalGrowthY ~= nil then ac.externals.growthY = data.externalGrowthY end
	end
	if data.externalOffsetX ~= nil then ac.externals.x = data.externalOffsetX end
	if data.externalOffsetY ~= nil then ac.externals.y = data.externalOffsetY end
	if data.externalSize ~= nil then ac.externals.size = data.externalSize end
	if data.externalIconShape ~= nil then
		ac.externals.iconShape = GF.NormalizeAuraIconShape(data.externalIconShape, ac.externals.iconShape or "DEFAULT")
		ac.externals.borderTexture = GF.NormalizeAuraBorderTexture(ac.externals.borderTexture, "DEFAULT", ac.externals.iconShape)
		ac.externals.glowStyle = GF.NormalizeExternalGlowStyleForIconShape(ac.externals.glowStyle, "MARCHING_ANTS", ac.externals.iconShape)
	end
	if data.externalPerRow ~= nil then ac.externals.perRow = data.externalPerRow end
	if data.externalMax ~= nil then ac.externals.max = GF.ClampAuraCount(data.externalMax, ac.externals.max or 4) or ac.externals.max or 4 end
	if data.externalSpacing ~= nil then ac.externals.spacing = data.externalSpacing end
	if data.externalGlowEnabled ~= nil then ac.externals.glowEnabled = data.externalGlowEnabled and true or false end
	if data.externalGlowColor ~= nil then ac.externals.glowColor = data.externalGlowColor end
	if data.externalGlowStyle ~= nil then ac.externals.glowStyle = GF.NormalizeExternalGlowStyleForIconShape(data.externalGlowStyle, ac.externals.glowStyle or "MARCHING_ANTS", ac.externals.iconShape) end
	if data.externalGlowInset ~= nil then ac.externals.glowInset = GF.NormalizeExternalGlowInset(data.externalGlowInset, ac.externals.glowInset or 0) end
	if data.externalBorderTexture ~= nil then ac.externals.borderTexture = GF.NormalizeAuraBorderTexture(data.externalBorderTexture, ac.externals.borderTexture or "DEFAULT", ac.externals.iconShape) end
	if data.externalBorderSize ~= nil then ac.externals.borderSize = clampNumber(data.externalBorderSize, 1, 64, ac.externals.borderSize or 2) end
	if data.externalBorderColor ~= nil then ac.externals.borderColor = data.externalBorderColor end
	if data.externalBorderOffset ~= nil then ac.externals.borderOffset = clampNumber(data.externalBorderOffset, -64, 64, ac.externals.borderOffset or 0) end
	if data.externalCooldownEnabled ~= nil then ac.externals.showCooldown = data.externalCooldownEnabled and true or false end
	if data.externalCooldownTextEnabled ~= nil then ac.externals.showCooldownText = data.externalCooldownTextEnabled and true or false end
	if data.externalCooldownTextAnchor ~= nil then ac.externals.cooldownAnchor = data.externalCooldownTextAnchor end
	if data.externalCooldownTextOffsetX ~= nil or data.externalCooldownTextOffsetY ~= nil then
		ac.externals.cooldownOffset = ac.externals.cooldownOffset or {}
		if data.externalCooldownTextOffsetX ~= nil then ac.externals.cooldownOffset.x = data.externalCooldownTextOffsetX end
		if data.externalCooldownTextOffsetY ~= nil then ac.externals.cooldownOffset.y = data.externalCooldownTextOffsetY end
	end
	if data.externalCooldownTextSize ~= nil then ac.externals.cooldownFontSize = data.externalCooldownTextSize end
	if data.externalCooldownTextFont ~= nil then ac.externals.cooldownFont = data.externalCooldownTextFont end
	if data.externalCooldownTextOutline ~= nil then ac.externals.cooldownFontOutline = data.externalCooldownTextOutline end
	if data.externalStackTextEnabled ~= nil then ac.externals.showStacks = data.externalStackTextEnabled and true or false end
	if data.externalStackAnchor ~= nil then ac.externals.countAnchor = data.externalStackAnchor end
	if data.externalStackOffsetX ~= nil or data.externalStackOffsetY ~= nil then
		ac.externals.countOffset = ac.externals.countOffset or {}
		if data.externalStackOffsetX ~= nil then ac.externals.countOffset.x = data.externalStackOffsetX end
		if data.externalStackOffsetY ~= nil then ac.externals.countOffset.y = data.externalStackOffsetY end
	end
	if data.externalStackSize ~= nil then ac.externals.countFontSize = data.externalStackSize end
	if data.externalStackFont ~= nil then ac.externals.countFont = data.externalStackFont end
	if data.externalStackOutline ~= nil then ac.externals.countFontOutline = data.externalStackOutline end
	if data.externalDrEnabled ~= nil then ac.externals.showDR = data.externalDrEnabled and true or false end
	if data.externalDrAnchor ~= nil then ac.externals.drAnchor = data.externalDrAnchor end
	if data.externalDrOffsetX ~= nil or data.externalDrOffsetY ~= nil then
		ac.externals.drOffset = ac.externals.drOffset or {}
		if data.externalDrOffsetX ~= nil then ac.externals.drOffset.x = data.externalDrOffsetX end
		if data.externalDrOffsetY ~= nil then ac.externals.drOffset.y = data.externalDrOffsetY end
	end
	if data.externalDrColor ~= nil then ac.externals.drColor = data.externalDrColor end
	if data.externalDrFontSize ~= nil then ac.externals.drFontSize = data.externalDrFontSize end
	if data.externalDrFont ~= nil then ac.externals.drFont = data.externalDrFont end
	if data.externalDrFontOutline ~= nil then ac.externals.drFontOutline = data.externalDrFontOutline end
	GFH.SyncAurasEnabled(cfg)
	if
		data.privateAurasEnabled ~= nil
		or data.privateAurasAmount ~= nil
		or data.privateAurasSize ~= nil
		or data.privateAurasTextScale ~= nil
		or data.privateAurasPoint ~= nil
		or data.privateAurasOffset ~= nil
		or data.privateAurasParentPoint ~= nil
		or data.privateAurasParentOffsetX ~= nil
		or data.privateAurasParentOffsetY ~= nil
		or data.privateAurasCountdownFrame ~= nil
		or data.privateAurasCountdownNumbers ~= nil
		or data.privateAurasShowTooltip ~= nil
		or data.privateAurasShowDispelType ~= nil
		or data.privateAurasDurationEnabled ~= nil
		or data.privateAurasDurationPoint ~= nil
		or data.privateAurasDurationOffsetX ~= nil
		or data.privateAurasDurationOffsetY ~= nil
	then
		cfg.privateAuras = cfg.privateAuras or {}
		cfg.privateAuras.icon = cfg.privateAuras.icon or {}
		cfg.privateAuras.parent = cfg.privateAuras.parent or {}
		cfg.privateAuras.duration = cfg.privateAuras.duration or {}
		if data.privateAurasEnabled ~= nil then cfg.privateAuras.enabled = data.privateAurasEnabled and true or false end
		if data.privateAurasAmount ~= nil then cfg.privateAuras.icon.amount = data.privateAurasAmount end
		if data.privateAurasSize ~= nil then cfg.privateAuras.icon.size = data.privateAurasSize end
		if data.privateAurasTextScale ~= nil then cfg.privateAuras.textScale = clampNumber(data.privateAurasTextScale, 1, 2.5, tonumber(cfg.privateAuras.textScale) or 1) end
		if data.privateAurasPoint ~= nil then cfg.privateAuras.icon.point = data.privateAurasPoint end
		if data.privateAurasOffset ~= nil then cfg.privateAuras.icon.offset = data.privateAurasOffset end
		if data.privateAurasParentPoint ~= nil then cfg.privateAuras.parent.point = data.privateAurasParentPoint end
		if data.privateAurasParentOffsetX ~= nil then cfg.privateAuras.parent.offsetX = data.privateAurasParentOffsetX end
		if data.privateAurasParentOffsetY ~= nil then cfg.privateAuras.parent.offsetY = data.privateAurasParentOffsetY end
		if data.privateAurasCountdownFrame ~= nil then cfg.privateAuras.countdownFrame = data.privateAurasCountdownFrame and true or false end
		if data.privateAurasCountdownNumbers ~= nil then cfg.privateAuras.countdownNumbers = data.privateAurasCountdownNumbers and true or false end
		if data.privateAurasShowTooltip ~= nil then cfg.privateAuras.showTooltip = data.privateAurasShowTooltip and true or false end
		if data.privateAurasShowDispelType ~= nil then cfg.privateAuras.showDispelType = data.privateAurasShowDispelType and true or false end
		if data.privateAurasDurationEnabled ~= nil then cfg.privateAuras.duration.enable = data.privateAurasDurationEnabled and true or false end
		if data.privateAurasDurationPoint ~= nil then cfg.privateAuras.duration.point = data.privateAurasDurationPoint end
		if data.privateAurasDurationOffsetX ~= nil then cfg.privateAuras.duration.offsetX = data.privateAurasDurationOffsetX end
		if data.privateAurasDurationOffsetY ~= nil then cfg.privateAuras.duration.offsetY = data.privateAurasDurationOffsetY end
	end

	if kind == "party" then
		if data.showPlayer ~= nil then cfg.showPlayer = data.showPlayer and true or false end
		if data.showSolo ~= nil then cfg.showSolo = data.showSolo and true or false end
		local custom = GFH.EnsureCustomSortConfig(cfg)
		local incomingSortMethod
		if data.sortMethod ~= nil then
			local sortMethod = tostring(data.sortMethod):upper()
			if sortMethod == "CUSTOM" then sortMethod = "NAMELIST" end
			if sortMethod ~= "INDEX" and sortMethod ~= "NAME" and sortMethod ~= "NAMELIST" then sortMethod = (DEFAULTS.party and DEFAULTS.party.sortMethod) or "INDEX" end
			incomingSortMethod = sortMethod
		end
		if data.sortDir ~= nil then
			local sortDir = tostring(data.sortDir):upper()
			cfg.sortDir = (GFH and GFH.NormalizeSortDir and GFH.NormalizeSortDir(sortDir)) or ((sortDir == "DESC") and "DESC" or "ASC")
		end
		if data.customSortEnabled == true then
			custom.enabled = true
			cfg.sortMethod = "NAMELIST"
		end
		if data.customSortPlayerFirstInRole ~= nil then custom.playerFirstInRole = data.customSortPlayerFirstInRole and true or false end
		if incomingSortMethod == "NAMELIST" then
			cfg.sortMethod = "NAMELIST"
			if custom then custom.enabled = true end
		elseif incomingSortMethod ~= nil then
			local defaultPartySort = (DEFAULTS.party and DEFAULTS.party.sortMethod) or "INDEX"
			local currentlyCustom = resolveSortMethod(cfg) == "NAMELIST" or (custom and custom.enabled == true)
			if not currentlyCustom then
				cfg.sortMethod = incomingSortMethod
				if custom then custom.enabled = false end
			elseif custom then
				-- Prevent stale EditMode defaults from downgrading a persisted custom party sort after reload.
				custom.enabled = true
				cfg.sortMethod = "NAMELIST"
			else
				cfg.sortMethod = defaultPartySort
			end
		end
	elseif isRaidLikeKind(kind) then
		if (kind == "mt" or kind == "ma") and data.viewerRoleFilter ~= nil then cfg.viewerRoleFilter = GF.NormalizeViewerRoleFilter(data.viewerRoleFilter) end
		if kind == "raid" then
			local custom = GFH.EnsureCustomSortConfig(cfg)
			local selectedRaidGroups
			local selectedRaidGroupCount
			local raidGroupSelectionChanged = false
			if data.customSortEnabled ~= nil then
				custom.enabled = data.customSortEnabled and true or false
				if custom.enabled then
					cfg.sortMethod = "NAMELIST"
				else
					local current = tostring(cfg.sortMethod or ""):upper()
					if current == "NAMELIST" or current == "CUSTOM" then cfg.sortMethod = (DEFAULTS.raid and DEFAULTS.raid.sortMethod) or "INDEX" end
				end
			end
			if data.customSortSeparateMeleeRanged ~= nil then
				custom.separateMeleeRanged = data.customSortSeparateMeleeRanged and true or false
				if custom.separateMeleeRanged then
					custom.roleOrder = GFH.ExpandRoleOrder(custom.roleOrder, true)
				else
					custom.roleOrder = GFH.CollapseRoleOrder(custom.roleOrder)
				end
			end
			if data.customSortPlayerFirstInRole ~= nil then custom.playerFirstInRole = data.customSortPlayerFirstInRole and true or false end
			if data.groupFilter ~= nil then
				local groupFilter = data.groupFilter
				if groupFilter == "" then groupFilter = nil end
				cfg.groupFilter = groupFilter
			else
				for group = 1, 8 do
					local field = data["raidGroup" .. group]
					if field ~= nil then
						if not selectedRaidGroups then
							selectedRaidGroups, selectedRaidGroupCount = GF.GetSelectedRaidGroups(cfg)
						end
						raidGroupSelectionChanged = true
						if field then
							if not selectedRaidGroups[group] then
								selectedRaidGroups[group] = true
								selectedRaidGroupCount = selectedRaidGroupCount + 1
							end
						elseif selectedRaidGroups[group] and selectedRaidGroupCount > 1 then
							selectedRaidGroups[group] = nil
							selectedRaidGroupCount = selectedRaidGroupCount - 1
						end
					end
				end
				if raidGroupSelectionChanged then cfg.groupFilter = GF.BuildRaidGroupFilter(selectedRaidGroups, cfg) end
			end
		end
		if data.unitsPerColumn ~= nil then
			local v = clampNumber(data.unitsPerColumn, 1, 10, cfg.unitsPerColumn or 5)
			cfg.unitsPerColumn = floor(v + 0.5)
		end
		if data.maxColumns ~= nil then
			local v = clampNumber(data.maxColumns, 1, 10, cfg.maxColumns or 8)
			cfg.maxColumns = floor(v + 0.5)
		end
		if data.groupsPerRow ~= nil then cfg.groupsPerRow = GF.NormalizeRaidGroupsPerRow(data.groupsPerRow, cfg.groupsPerRow or (DEFAULTS.raid and DEFAULTS.raid.groupsPerRow)) end
		if data.dynamicScaleMin ~= nil then cfg.dynamicScaleMin = clampNumber(data.dynamicScaleMin, 0, 1, cfg.dynamicScaleMin or 0) end
		if data.dynamicContentScale ~= nil and data.dynamicScaleMin == nil then cfg.dynamicScaleMin = data.dynamicContentScale and 1 or 0 end
		if data.columnSpacing ~= nil then cfg.columnSpacing = clampNumber(data.columnSpacing, 0, 40, cfg.columnSpacing or 0) end
	end

	local refreshNames = data.showName ~= nil
		or data.nameAnchor ~= nil
		or data.nameOffsetX ~= nil
		or data.nameOffsetY ~= nil
		or data.nameMaxChars ~= nil
		or data.nameNoEllipsis ~= nil
		or data.nameFontSize ~= nil
		or data.nameFont ~= nil
		or data.nameFontOutline ~= nil
		or data.nameStrata ~= nil
		or data.nameFrameLevelOffset ~= nil
		or data.nameClassColor ~= nil
		or data.nameColor ~= nil
	local refreshRangeFade = data.rangeFadeEnabled ~= nil or data.rangeFadeAlpha ~= nil or data.rangeFadeOfflineAlpha ~= nil

	GF:ApplyHeaderAttributes(kind)
	if data.dynamicScaleMin ~= nil or data.dynamicContentScale ~= nil then
		refreshAllPrivateAuras()
		if kind == "raid" then GF:RefreshGroupIndicators() end
	end
	if data.hideInClientScene ~= nil then GF:RefreshClientSceneVisibility() end
	if refreshNames then GF:RefreshNames() end
	if refreshAuras then refreshAllAuras() end
	if refreshRangeFade then GF:RefreshRangeFade() end
	if positionChanged and addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
	if positionChanged and EditMode and EditMode.RefreshFrame and not GF._syncingGroupFrameEditModePosition then
		local editModeId = EDITMODE_IDS and EDITMODE_IDS[kind]
		if editModeId then
			GF._syncingGroupFrameEditModePosition = true
			EditMode:RefreshFrame(editModeId)
			GF._syncingGroupFrameEditModePosition = false
		end
	end
end

function GF:EnsureEditMode()
	if GF._editModeRegistered then return end
	if not isFeatureEnabled() then return end
	if not (EditMode and EditMode.RegisterFrame and EditMode.IsAvailable and EditMode:IsAvailable()) then return end

	GF:EnsureHeaders()

	for _, kind in ipairs({ "party", "raid", "mt", "ma" }) do
		local anchor = GF.anchors and GF.anchors[kind]
		if anchor then
			GF:UpdateAnchorSize(kind)
			local cfg = getCfg(kind)
			local ac = ensureAuraConfig(cfg)
			local pcfg = cfg.power or {}
			local rc = cfg.roleIcon or {}
			local tcfg = cfg.tooltip or {}
			local sc = cfg.status or {}
			local gn = sc.groupNumber or {}
			local gi = cfg.groupIndicator or {}
			local lc = sc.leaderIcon or {}
			local racfg = sc.raidAssignmentIcon or {}
			local acfg = sc.assistIcon or {}
			local hc = cfg.health or {}
			local bc = cfg.border or {}
			local def = DEFAULTS[kind] or {}
			local defStatus = def.status or {}
			local defUS = defStatus.unitStatus or {}
			local defGN = defStatus.groupNumber or {}
			local defGI = def.groupIndicator or {}
			local defTooltip = def.tooltip or {}
			local defH = def.health or {}
			local defP = def.power or {}
			local defAuras = def.auras or {}
			local defPrivate = def.privateAuras or {}
			local defBuff = defAuras.buff or {}
			local defDebuff = defAuras.debuff or {}
			local defExt = defAuras.externals or {}
			local pa = cfg.privateAuras or {}
			local paIcon = pa.icon or {}
			local paParent = pa.parent or {}
			local paDuration = pa.duration or {}
			local defPrivateIcon = defPrivate.icon or {}
			local defPrivateParent = defPrivate.parent or {}
			local defPrivateDuration = defPrivate.duration or {}
			local defRangeFade = def.status and def.status.rangeFade or {}
			local defDispel = def.status and def.status.dispelTint or {}
			local hcBackdrop = hc.backdrop or {}
			local defHBackdrop = defH.backdrop or {}
			local pcfgBackdrop = pcfg.backdrop or {}
			local defPBackdrop = defP.backdrop or {}
			local buffAnchor = ac.buff.anchorPoint or "TOPLEFT"
			local _, buffPrimary, buffSecondary = resolveAuraGrowth(buffAnchor, ac.buff.growth, ac.buff.growthX, ac.buff.growthY)
			local buffGrowth = growthPairToString(buffPrimary, buffSecondary)
			local debuffAnchor = ac.debuff.anchorPoint or "BOTTOMLEFT"
			local _, debuffPrimary, debuffSecondary = resolveAuraGrowth(debuffAnchor, ac.debuff.growth, ac.debuff.growthX, ac.debuff.growthY)
			local debuffGrowth = growthPairToString(debuffPrimary, debuffSecondary)
			local externalAnchor = ac.externals.anchorPoint or "TOPRIGHT"
			local _, externalPrimary, externalSecondary = resolveAuraGrowth(externalAnchor, ac.externals.growth, ac.externals.growthX, ac.externals.growthY)
			local externalGrowth = growthPairToString(externalPrimary, externalSecondary)
			local overlayHeightFallback = GF._computeOverlayHeightFallback((cfg.height ~= nil and cfg.height) or def.height, (cfg.powerHeight ~= nil and cfg.powerHeight) or def.powerHeight)
			local absorbOverlayHeightValue = GF._resolveOverlayHeightSetting(hc.absorbOverlayHeight ~= nil and hc.absorbOverlayHeight or defH.absorbOverlayHeight, overlayHeightFallback)
			local healAbsorbOverlayHeightValue =
				GF._resolveOverlayHeightSetting(hc.healAbsorbOverlayHeight ~= nil and hc.healAbsorbOverlayHeight or defH.healAbsorbOverlayHeight, overlayHeightFallback)
			local absorbReverseValue = hc.absorbReverseFill
			if absorbReverseValue == nil then absorbReverseValue = defH.absorbReverseFill == true end
			local absorbDontOverflowValue = hc.absorbDontOverflowHealthBar
			if absorbDontOverflowValue == nil then absorbDontOverflowValue = defH.absorbDontOverflowHealthBar == true end
			absorbDontOverflowValue = absorbDontOverflowValue == true and absorbReverseValue == true
			local absorbGlowValue = hc.useAbsorbGlow
			if absorbGlowValue == nil then absorbGlowValue = defH.useAbsorbGlow == true end
			local healAbsorbReverseValue = hc.healAbsorbReverseFill
			if healAbsorbReverseValue == nil then healAbsorbReverseValue = defH.healAbsorbReverseFill == true end
			local healAbsorbDontOverflowValue = hc.healAbsorbDontOverflowHealthBar
			if healAbsorbDontOverflowValue == nil then healAbsorbDontOverflowValue = defH.healAbsorbDontOverflowHealthBar ~= false end
			healAbsorbDontOverflowValue = healAbsorbDontOverflowValue == true and healAbsorbReverseValue ~= true
			local absorbOverlayAnchorTopValue = hc.absorbOverlayAnchorTop
			if absorbOverlayAnchorTopValue == nil then absorbOverlayAnchorTopValue = defH.absorbOverlayAnchorTop == true end
			local healAbsorbOverlayAnchorTopValue = hc.healAbsorbOverlayAnchorTop
			if healAbsorbOverlayAnchorTopValue == nil then healAbsorbOverlayAnchorTopValue = defH.healAbsorbOverlayAnchorTop == true end
			local _, resolvedGrowth = GF.ResolveUnitGrowthDirection(cfg.growth, "DOWN")
			local defaults = {
				point = cfg.point or "CENTER",
				relativePoint = cfg.relativePoint or cfg.point or "CENTER",
				anchorTarget = GF.GetGroupAnchorTargetValue(kind),
				x = cfg.x or 0,
				y = cfg.y or 0,
				width = cfg.width or (DEFAULTS[kind] and DEFAULTS[kind].width) or 100,
				height = cfg.height or (DEFAULTS[kind] and DEFAULTS[kind].height) or 24,
				powerHeight = cfg.powerHeight or (DEFAULTS[kind] and DEFAULTS[kind].powerHeight) or 6,
				spacing = cfg.spacing or (DEFAULTS[kind] and DEFAULTS[kind].spacing) or 0,
				growth = cfg.growth or (DEFAULTS[kind] and DEFAULTS[kind].growth) or "DOWN",
				groupGrowth = (kind == "raid") and ((GFH.ResolveGroupGrowthDirection and GFH.ResolveGroupGrowthDirection(
					cfg.groupGrowth,
					resolvedGrowth,
					DEFAULTS and DEFAULTS.raid and DEFAULTS.raid.groupGrowth
				)) or ((GFH.NormalizeGrowthDirection and GFH.NormalizeGrowthDirection(cfg.groupGrowth, nil)) or ((resolvedGrowth == "RIGHT" or resolvedGrowth == "LEFT") and "DOWN" or "RIGHT"))) or nil,
				barTexture = usesPerBarTextureSelection(cfg) and BAR_TEX_INHERIT or cfg.barTexture,
				borderEnabled = (cfg.border and cfg.border.enabled) ~= false,
				borderColor = (cfg.border and cfg.border.color) or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.color) or { 0, 0, 0, 0.8 },
				borderTexture = (cfg.border and cfg.border.texture) or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.texture) or "DEFAULT",
				borderSize = (cfg.border and cfg.border.edgeSize) or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.edgeSize) or 1,
				borderOffset = (cfg.border and (cfg.border.offset or cfg.border.inset))
					or (DEFAULTS[kind] and DEFAULTS[kind].border and (DEFAULTS[kind].border.offset or DEFAULTS[kind].border.inset))
					or (cfg.border and cfg.border.edgeSize)
					or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.edgeSize)
					or 1,
				borderStrata = (function()
					local value = (cfg.border and cfg.border.strata) or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.strata) or ""
					value = tostring(value or ""):upper()
					if value == "DEFAULT" then value = "" end
					if
						value ~= ""
						and value ~= "BACKGROUND"
						and value ~= "LOW"
						and value ~= "MEDIUM"
						and value ~= "HIGH"
						and value ~= "DIALOG"
						and value ~= "FULLSCREEN"
						and value ~= "FULLSCREEN_DIALOG"
						and value ~= "TOOLTIP"
					then
						value = ""
					end
					return value
				end)(),
				borderFrameLevelOffset = (function()
					local value = (cfg.border and cfg.border.frameLevelOffset)
					if value == nil then value = (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.frameLevelOffset) end
					value = clampNumber(value, -20, 1000, 3)
					return floor(value + (value >= 0 and 0.5 or -0.5))
				end)(),
				groupBorderEnabled = kind == "party" and ((cfg.groupBorder and cfg.groupBorder.enabled) == true) or nil,
				groupBorderColor = kind == "party"
						and ((cfg.groupBorder and cfg.groupBorder.color) or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.color) or { 1, 1, 1, 1 })
					or nil,
				groupBorderTexture = kind == "party"
						and ((cfg.groupBorder and cfg.groupBorder.texture) or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.texture) or "DEFAULT")
					or nil,
				groupBorderSize = kind == "party" and ((cfg.groupBorder and cfg.groupBorder.edgeSize) or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.edgeSize) or 1)
					or nil,
				groupBorderOffset = kind == "party"
						and ((cfg.groupBorder and (cfg.groupBorder.offset or cfg.groupBorder.inset)) or (DEFAULTS.party and DEFAULTS.party.groupBorder and (DEFAULTS.party.groupBorder.offset or DEFAULTS.party.groupBorder.inset)) or (cfg.groupBorder and cfg.groupBorder.edgeSize) or (DEFAULTS.party and DEFAULTS.party.groupBorder and DEFAULTS.party.groupBorder.edgeSize) or 1)
					or nil,
				hoverHighlightEnabled = (cfg.highlightHover and cfg.highlightHover.enabled) == true,
				hoverHighlightColor = (cfg.highlightHover and cfg.highlightHover.color) or (def.highlightHover and def.highlightHover.color) or { 1, 1, 1, 0.9 },
				hoverHighlightTexture = (cfg.highlightHover and cfg.highlightHover.texture) or (def.highlightHover and def.highlightHover.texture) or "DEFAULT",
				hoverHighlightStrata = GF.NormalizeFrameStrataToken((cfg.highlightHover and cfg.highlightHover.strata) or (def.highlightHover and def.highlightHover.strata)) or "",
				hoverHighlightFrameLevelOffset = (cfg.highlightHover and cfg.highlightHover.frameLevelOffset) or (def.highlightHover and def.highlightHover.frameLevelOffset) or 4,
				hoverHighlightSize = (cfg.highlightHover and cfg.highlightHover.size) or (def.highlightHover and def.highlightHover.size) or 2,
				hoverHighlightOffset = (cfg.highlightHover and cfg.highlightHover.offset) or (def.highlightHover and def.highlightHover.offset) or 0,
				aggroHighlightEnabled = (cfg.highlightAggro and cfg.highlightAggro.enabled) == true,
				aggroHighlightColor = (cfg.highlightAggro and cfg.highlightAggro.color) or (def.highlightAggro and def.highlightAggro.color) or { 1, 0.55, 0, 1 },
				aggroHighlightMode = GF.NormalizeAggroHighlightMode((cfg.highlightAggro and cfg.highlightAggro.mode) or (def.highlightAggro and def.highlightAggro.mode) or "ALL"),
				aggroHighlightSample = (cfg.highlightAggro and cfg.highlightAggro.sample) == true
					or ((cfg.highlightAggro == nil or cfg.highlightAggro.sample == nil) and (def.highlightAggro and def.highlightAggro.sample == true)),
				aggroHighlightTexture = (cfg.highlightAggro and cfg.highlightAggro.texture) or (def.highlightAggro and def.highlightAggro.texture) or "DEFAULT",
				aggroHighlightLayer = (function()
					local layer = tostring((cfg.highlightAggro and cfg.highlightAggro.layer) or (def.highlightAggro and def.highlightAggro.layer) or "ABOVE_BORDER"):upper()
					if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
					return layer
				end)(),
				aggroHighlightSize = (cfg.highlightAggro and cfg.highlightAggro.size) or (def.highlightAggro and def.highlightAggro.size) or 2,
				aggroHighlightOffset = (cfg.highlightAggro and cfg.highlightAggro.offset) or (def.highlightAggro and def.highlightAggro.offset) or 0,
				targetHighlightEnabled = (cfg.highlightTarget and cfg.highlightTarget.enabled) == true,
				targetHighlightColor = (cfg.highlightTarget and cfg.highlightTarget.color) or (def.highlightTarget and def.highlightTarget.color) or { 1, 1, 0, 1 },
				targetHighlightTexture = (cfg.highlightTarget and cfg.highlightTarget.texture) or (def.highlightTarget and def.highlightTarget.texture) or "DEFAULT",
				targetHighlightLayer = (function()
					local layer = tostring((cfg.highlightTarget and cfg.highlightTarget.layer) or (def.highlightTarget and def.highlightTarget.layer) or "ABOVE_BORDER"):upper()
					if layer ~= "BEHIND_BORDER" then layer = "ABOVE_BORDER" end
					return layer
				end)(),
				targetHighlightSize = (cfg.highlightTarget and cfg.highlightTarget.size) or (def.highlightTarget and def.highlightTarget.size) or 2,
				targetHighlightOffset = (cfg.highlightTarget and cfg.highlightTarget.offset) or (def.highlightTarget and def.highlightTarget.offset) or 0,
				tooltipMode = tcfg.mode or defTooltip.mode or "OFF",
				tooltipModifier = tcfg.modifier or defTooltip.modifier or "ALT",
				tooltipUseEditMode = (tcfg.useEditMode ~= nil and tcfg.useEditMode == true) or ((tcfg.useEditMode == nil) and defTooltip.useEditMode == true),
				tooltipAuras = ac.buff.showTooltip == true and ac.debuff.showTooltip == true and ac.externals.showTooltip == true,
				showPlayer = cfg.showPlayer == true,
				showSolo = cfg.showSolo == true,
				viewerRoleFilter = GF.NormalizeViewerRoleFilter(cfg.viewerRoleFilter or (DEFAULTS[kind] and DEFAULTS[kind].viewerRoleFilter)),
				hideInClientScene = (cfg.hideInClientScene ~= nil and cfg.hideInClientScene == true) or ((cfg.hideInClientScene == nil) and (def.hideInClientScene ~= false)),
				unitsPerColumn = cfg.unitsPerColumn or (DEFAULTS[kind] and DEFAULTS[kind].unitsPerColumn) or (DEFAULTS.raid and DEFAULTS.raid.unitsPerColumn) or 5,
				maxColumns = cfg.maxColumns or (DEFAULTS[kind] and DEFAULTS[kind].maxColumns) or (DEFAULTS.raid and DEFAULTS.raid.maxColumns) or 8,
				groupsPerRow = kind == "raid" and GF.NormalizeRaidGroupsPerRow(cfg.groupsPerRow, DEFAULTS.raid and DEFAULTS.raid.groupsPerRow) or nil,
				dynamicScaleMin = GF.GetDynamicContentScaleAmount(cfg),
				columnSpacing = cfg.columnSpacing or (DEFAULTS[kind] and DEFAULTS[kind].columnSpacing) or (DEFAULTS.raid and DEFAULTS.raid.columnSpacing) or 0,
				customSortEnabled = resolveSortMethod(cfg) == "NAMELIST",
				customSortSeparateMeleeRanged = (cfg.customSort and cfg.customSort.separateMeleeRanged) == true,
				customSortPlayerFirstInRole = (cfg.customSort and cfg.customSort.playerFirstInRole) == true,
				raidGroup1 = GF.IsRaidGroupShown(cfg, 1),
				raidGroup2 = GF.IsRaidGroupShown(cfg, 2),
				raidGroup3 = GF.IsRaidGroupShown(cfg, 3),
				raidGroup4 = GF.IsRaidGroupShown(cfg, 4),
				raidGroup5 = GF.IsRaidGroupShown(cfg, 5),
				raidGroup6 = GF.IsRaidGroupShown(cfg, 6),
				raidGroup7 = GF.IsRaidGroupShown(cfg, 7),
				raidGroup8 = GF.IsRaidGroupShown(cfg, 8),
				showName = (cfg.text and cfg.text.showName) ~= false,
				nameClassColor = (cfg.text and cfg.text.useClassColor) ~= false,
				nameAnchor = (cfg.text and cfg.text.nameAnchor) or (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameAnchor) or "LEFT",
				nameOffsetX = (cfg.text and cfg.text.nameOffset and cfg.text.nameOffset.x) or 0,
				nameOffsetY = (cfg.text and cfg.text.nameOffset and cfg.text.nameOffset.y) or 0,
				nameMaxChars = (cfg.text and cfg.text.nameMaxChars) or (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameMaxChars) or 0,
				nameNoEllipsis = (cfg.text and cfg.text.nameNoEllipsis ~= nil) and (cfg.text.nameNoEllipsis == true)
					or ((cfg.text == nil or cfg.text.nameNoEllipsis == nil) and (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.nameNoEllipsis) == true),
				nameFontSize = (cfg.text and cfg.text.fontSize) or (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.fontSize) or 12,
				nameFont = (cfg.text and cfg.text.font) or GF.GlobalFontConfigKey(),
				nameFontOutline = (cfg.text and cfg.text.fontOutline) or (DEFAULTS[kind] and DEFAULTS[kind].text and DEFAULTS[kind].text.fontOutline) or "OUTLINE",
				nameStrata = GF.NormalizeFrameStrataToken(sc.nameStrata) or "",
				nameFrameLevelOffset = sc.nameFrameLevelOffset or 5,
				healthClassColor = (cfg.health and cfg.health.useClassColor) == true,
				healthUseCustomColor = (cfg.health and cfg.health.useCustomColor) == true,
				healthColor = (cfg.health and cfg.health.color) or ((DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.color) or { 0, 0.8, 0, 1 }),
				healthTextLeft = (cfg.health and cfg.health.textLeft) or ((DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textLeft) or "NONE"),
				healthTextCenter = (cfg.health and cfg.health.textCenter) or ((DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textCenter) or "NONE"),
				healthTextRight = (cfg.health and cfg.health.textRight) or ((DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textRight) or "NONE"),
				healthTextColor = (cfg.health and cfg.health.textColor) or ((DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textColor) or { 1, 1, 1, 1 }),
				healthTextClassColor = (cfg.health and cfg.health.useTextClassColor) == true,
				healthDelimiter = (cfg.health and cfg.health.textDelimiter) or ((DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiter) or " "),
				healthDelimiterSecondary = (cfg.health and cfg.health.textDelimiterSecondary)
					or ((DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterSecondary) or ((cfg.health and cfg.health.textDelimiter) or " ")),
				healthDelimiterTertiary = (cfg.health and cfg.health.textDelimiterTertiary)
					or (
						(DEFAULTS[kind] and DEFAULTS[kind].health and DEFAULTS[kind].health.textDelimiterTertiary)
						or ((cfg.health and cfg.health.textDelimiterSecondary) or (cfg.health and cfg.health.textDelimiter) or " ")
					),
				healthShortNumbers = (cfg.health and cfg.health.useShortNumbers) ~= false,
				healthRoundPercent = (cfg.health and cfg.health.roundPercent) == true,
				healthHidePercent = (cfg.health and cfg.health.hidePercentSymbol) == true,
				healthFontSize = hc.fontSize or defH.fontSize or 12,
				healthFont = hc.font or defH.font or nil,
				healthFontOutline = hc.fontOutline or defH.fontOutline or "OUTLINE",
				healthTexture = hc.texture or defH.texture or "DEFAULT",
				healthSmoothFill = (hc.smoothFill ~= nil) and (hc.smoothFill == true) or (defH.smoothFill == true),
				healthTempMaxHealthLoss = (hc.tempMaxHealthLossEnabled ~= nil) and (hc.tempMaxHealthLossEnabled ~= false) or ((hc.tempMaxHealthLossEnabled == nil) and (defH.tempMaxHealthLossEnabled ~= false)),
				healthBackdropEnabled = (hcBackdrop.enabled ~= nil) and (hcBackdrop.enabled ~= false) or (defHBackdrop.enabled ~= false),
				healthBackdropClampToFill = (hcBackdrop.clampToFill ~= nil) and (hcBackdrop.clampToFill == true) or ((hcBackdrop.clampToFill == nil) and (defHBackdrop.clampToFill == true)),
				healthBackdropColor = hcBackdrop.color or defHBackdrop.color or { 0, 0, 0, 0.6 },
				healthBackdropTexture = hcBackdrop.texture or defHBackdrop.texture or "DEFAULT",
				healthLeftX = (cfg.health and cfg.health.offsetLeft and cfg.health.offsetLeft.x) or 0,
				healthLeftY = (cfg.health and cfg.health.offsetLeft and cfg.health.offsetLeft.y) or 0,
				healthCenterX = (cfg.health and cfg.health.offsetCenter and cfg.health.offsetCenter.x) or 0,
				healthCenterY = (cfg.health and cfg.health.offsetCenter and cfg.health.offsetCenter.y) or 0,
				healthRightX = (cfg.health and cfg.health.offsetRight and cfg.health.offsetRight.x) or 0,
				healthRightY = (cfg.health and cfg.health.offsetRight and cfg.health.offsetRight.y) or 0,
				incomingHealEnabled = (cfg.health and cfg.health.incomingHealEnabled) == true,
				incomingHealSample = hc.showSampleIncomingHeal == true,
				absorbEnabled = (cfg.health and cfg.health.absorbEnabled) ~= false,
				absorbSample = hc.showSampleAbsorb == true,
				absorbTexture = (cfg.health and cfg.health.absorbTexture) or "SOLID",
				absorbReverse = absorbReverseValue == true,
				absorbDontOverflow = absorbDontOverflowValue == true,
				absorbOverlayHeight = absorbOverlayHeightValue,
				absorbOverlayAnchorTop = absorbOverlayAnchorTopValue == true,
				absorbUseCustomColor = (cfg.health and cfg.health.absorbUseCustomColor) == true,
				absorbColor = (cfg.health and cfg.health.absorbColor) or { 0.85, 0.95, 1, 0.7 },
				absorbGlow = absorbGlowValue == true,
				healAbsorbEnabled = (cfg.health and cfg.health.healAbsorbEnabled) ~= false,
				healAbsorbSample = hc.showSampleHealAbsorb == true,
				healAbsorbTexture = (cfg.health and cfg.health.healAbsorbTexture) or "SOLID",
				healAbsorbReverse = healAbsorbReverseValue == true,
				healAbsorbDontOverflow = healAbsorbDontOverflowValue == true,
				healAbsorbOverlayHeight = healAbsorbOverlayHeightValue,
				healAbsorbOverlayAnchorTop = healAbsorbOverlayAnchorTopValue == true,
				healAbsorbUseCustomColor = (cfg.health and cfg.health.healAbsorbUseCustomColor) == true,
				healAbsorbColor = (cfg.health and cfg.health.healAbsorbColor) or { 1, 0.3, 0.3, 0.7 },
				nameColorMode = sc.nameColorMode or (((cfg.text and cfg.text.useClassColor) ~= false) and "CLASS" or "CUSTOM"),
				nameColor = sc.nameColor or { 1, 1, 1, 1 },
				levelEnabled = sc.levelEnabled ~= false,
				hideLevelAtMax = sc.hideLevelAtMax == true,
				levelClassColor = (sc.levelColorMode or "CUSTOM") == "CLASS",
				levelColorMode = sc.levelColorMode or "CUSTOM",
				levelColor = sc.levelColor or { 1, 0.85, 0, 1 },
				levelFontSize = sc.levelFontSize or (cfg.text and cfg.text.fontSize) or (cfg.health and cfg.health.fontSize) or 12,
				levelFont = sc.levelFont or GF.GlobalFontConfigKey(),
				levelFontOutline = sc.levelFontOutline or (cfg.text and cfg.text.fontOutline) or (cfg.health and cfg.health.fontOutline) or "OUTLINE",
				levelAnchor = sc.levelAnchor or "RIGHT",
				levelStrata = GF.NormalizeFrameStrataToken(sc.levelStrata) or "",
				levelFrameLevelOffset = sc.levelFrameLevelOffset or 5,
				levelOffsetX = (sc.levelOffset and sc.levelOffset.x) or 0,
				levelOffsetY = (sc.levelOffset and sc.levelOffset.y) or 0,
				statusTextEnabled = (sc.unitStatus and sc.unitStatus.enabled) ~= false,
				statusTextColor = (sc.unitStatus and sc.unitStatus.color) or (def.status and def.status.unitStatus and def.status.unitStatus.color) or { 1, 1, 1, 1 },
				statusTextFontSize = (sc.unitStatus and sc.unitStatus.fontSize) or (cfg.health and cfg.health.fontSize) or (defH and defH.fontSize) or 12,
				statusTextFont = (sc.unitStatus and sc.unitStatus.font) or (cfg.health and cfg.health.font) or (defH and defH.font) or nil,
				statusTextFontOutline = (sc.unitStatus and sc.unitStatus.fontOutline) or (cfg.health and cfg.health.fontOutline) or (defH and defH.fontOutline) or "OUTLINE",
				statusTextAnchor = (sc.unitStatus and sc.unitStatus.anchor) or (def.status and def.status.unitStatus and def.status.unitStatus.anchor) or "CENTER",
				statusTextOffsetX = (sc.unitStatus and sc.unitStatus.offset and sc.unitStatus.offset.x) or 0,
				statusTextOffsetY = (sc.unitStatus and sc.unitStatus.offset and sc.unitStatus.offset.y) or 0,
				statusTextShowOffline = (function()
					local value = sc.unitStatus and sc.unitStatus.showOffline
					if value == nil then value = def.status and def.status.unitStatus and def.status.unitStatus.showOffline end
					if value == nil then value = true end
					return value
				end)(),
				statusTextShowAFK = (function()
					local value = sc.unitStatus and sc.unitStatus.showAFK
					if value == nil then value = def.status and def.status.unitStatus and def.status.unitStatus.showAFK end
					if value == nil then value = false end
					return value
				end)(),
				statusTextShowDead = (function()
					local value = sc.unitStatus and sc.unitStatus.showDead
					if value == nil then value = def.status and def.status.unitStatus and def.status.unitStatus.showDead end
					if value == nil then value = true end
					return value
				end)(),
				statusTextShowDND = (function()
					local value = sc.unitStatus and sc.unitStatus.showDND
					if value == nil then value = def.status and def.status.unitStatus and def.status.unitStatus.showDND end
					if value == nil then value = false end
					return value
				end)(),
				statusTextShowGhost = (function()
					local value = sc.unitStatus and sc.unitStatus.showGhost
					if value == nil then value = def.status and def.status.unitStatus and def.status.unitStatus.showGhost end
					if value == nil then value = true end
					return value
				end)(),
				statusTextShowGroup = (gn.enabled ~= nil and gn.enabled) or (sc.unitStatus and sc.unitStatus.showGroup) or (defGN.enabled ~= nil and defGN.enabled) or defUS.showGroup or false,
				statusTextGroupFormat = gn.format or (sc.unitStatus and sc.unitStatus.groupFormat) or defGN.format or defUS.groupFormat or "GROUP",
				groupNumberColor = gn.color or defGN.color or (sc.unitStatus and sc.unitStatus.color) or defUS.color or { 1, 1, 1, 1 },
				groupNumberFontSize = gn.fontSize
					or defGN.fontSize
					or (sc.unitStatus and sc.unitStatus.fontSize)
					or defUS.fontSize
					or (cfg.health and cfg.health.fontSize)
					or (defH and defH.fontSize)
					or 12,
				groupNumberFont = gn.font or defGN.font or (sc.unitStatus and sc.unitStatus.font) or defUS.font or (cfg.health and cfg.health.font) or (defH and defH.font) or nil,
				groupNumberFontOutline = gn.fontOutline
					or defGN.fontOutline
					or (sc.unitStatus and sc.unitStatus.fontOutline)
					or defUS.fontOutline
					or (cfg.health and cfg.health.fontOutline)
					or (defH and defH.fontOutline)
					or "OUTLINE",
				groupNumberAnchor = gn.anchor or defGN.anchor or (sc.unitStatus and sc.unitStatus.anchor) or defUS.anchor or "CENTER",
				groupNumberOffsetX = (gn.offset and gn.offset.x)
					or (defGN.offset and defGN.offset.x)
					or (sc.unitStatus and sc.unitStatus.offset and sc.unitStatus.offset.x)
					or (defUS.offset and defUS.offset.x)
					or 0,
				groupNumberOffsetY = (gn.offset and gn.offset.y)
					or (defGN.offset and defGN.offset.y)
					or (sc.unitStatus and sc.unitStatus.offset and sc.unitStatus.offset.y)
					or (defUS.offset and defUS.offset.y)
					or 0,
				groupIndicatorEnabled = (gi.enabled ~= nil and gi.enabled == true) or ((gi.enabled == nil) and (defGI.enabled == true)),
				groupIndicatorHidePerFrame = (gi.hidePerFrame ~= nil and gi.hidePerFrame == true) or ((gi.hidePerFrame == nil) and (defGI.hidePerFrame == true)),
				groupIndicatorFormat = gi.format or defGI.format or "GROUP",
				groupIndicatorColor = gi.color or defGI.color or { 1, 1, 1, 1 },
				groupIndicatorFontSize = gi.fontSize or defGI.fontSize or (cfg.health and cfg.health.fontSize) or (defH and defH.fontSize) or 12,
				groupIndicatorFont = gi.font or defGI.font or (cfg.health and cfg.health.font) or (defH and defH.font) or nil,
				groupIndicatorFontOutline = gi.fontOutline or defGI.fontOutline or (cfg.health and cfg.health.fontOutline) or (defH and defH.fontOutline) or "OUTLINE",
				groupIndicatorAnchor = gi.anchor or defGI.anchor or "TOPLEFT",
				groupIndicatorOffsetX = (gi.offset and gi.offset.x) or (defGI.offset and defGI.offset.x) or 0,
				groupIndicatorOffsetY = (gi.offset and gi.offset.y) or (defGI.offset and defGI.offset.y) or 0,
				statusTextHideHealthTextOffline = (sc.unitStatus and sc.unitStatus.hideHealthTextWhenOffline)
					or (def.status and def.status.unitStatus and def.status.unitStatus.hideHealthTextWhenOffline)
					or false,
				rangeFadeEnabled = (sc.rangeFade and sc.rangeFade.enabled ~= nil) and (sc.rangeFade.enabled ~= false)
					or ((sc.rangeFade == nil or sc.rangeFade.enabled == nil) and defRangeFade.enabled ~= false),
				rangeFadeAlpha = (function()
					local value = sc.rangeFade and sc.rangeFade.alpha
					if value == nil then value = defRangeFade.alpha end
					if value == nil then value = 0.55 end
					return value
				end)(),
				rangeFadeOfflineAlpha = (function()
					local value = sc.rangeFade and sc.rangeFade.offlineAlpha
					if value == nil then value = defRangeFade.offlineAlpha end
					if value == nil then value = sc.rangeFade and sc.rangeFade.alpha end
					if value == nil then value = defRangeFade.alpha end
					if value == nil then value = 0.4 end
					return value
				end)(),
				dispelTintEnabled = (sc.dispelTint and sc.dispelTint.enabled ~= nil) and (sc.dispelTint.enabled ~= false)
					or ((sc.dispelTint == nil or sc.dispelTint.enabled == nil) and defDispel.enabled ~= false),
				dispelTintBlizzardPrivateAuraDispels = GF.ResolveBlizzardPrivateAuraDispelsEnabled(cfg, def),
				dispelTintBlizzardDispelMode = GF.GetDispelTintBlizzardDispelIndicatorMode(cfg, def),
				dispelTintAlpha = (sc.dispelTint and sc.dispelTint.alpha) or defDispel.alpha or 0.25,
				dispelTintFillEnabled = (sc.dispelTint and sc.dispelTint.fillEnabled ~= nil) and (sc.dispelTint.fillEnabled == true)
					or ((sc.dispelTint == nil or sc.dispelTint.fillEnabled == nil) and defDispel.fillEnabled ~= false),
				dispelTintFillAlpha = (sc.dispelTint and sc.dispelTint.fillAlpha) or defDispel.fillAlpha or 0.2,
				dispelTintFillColor = (sc.dispelTint and sc.dispelTint.fillColor) or defDispel.fillColor or { 0, 0, 0, 1 },
				dispelTintStrata = GF.NormalizeFrameStrataToken(sc.dispelTint and sc.dispelTint.strata) or "",
				dispelTintFrameLevelOffset = (sc.dispelTint and sc.dispelTint.frameLevelOffset) or 1,
				dispelTintSample = (sc.dispelTint and sc.dispelTint.showSample ~= nil) and (sc.dispelTint.showSample == true)
					or ((sc.dispelTint == nil or sc.dispelTint.showSample == nil) and defDispel.showSample == true),
				dispelTintGlowEnabled = (sc.dispelTint and sc.dispelTint.glowEnabled ~= nil) and (sc.dispelTint.glowEnabled == true)
					or ((sc.dispelTint == nil or sc.dispelTint.glowEnabled == nil) and defDispel.glowEnabled == true),
				dispelTintGlowOverOverlay = (sc.dispelTint and sc.dispelTint.glowOverOverlay ~= nil) and (sc.dispelTint.glowOverOverlay == true)
					or ((sc.dispelTint == nil or sc.dispelTint.glowOverOverlay == nil) and defDispel.glowOverOverlay == true),
				dispelTintGlowColorMode = (sc.dispelTint and sc.dispelTint.glowColorMode) or defDispel.glowColorMode or "DISPEL",
				dispelTintGlowColor = (sc.dispelTint and sc.dispelTint.glowColor) or defDispel.glowColor or { 1, 1, 1, 1 },
				dispelTintGlowEffect = (sc.dispelTint and sc.dispelTint.glowEffect) or defDispel.glowEffect or "PIXEL",
				dispelTintGlowFrequency = (sc.dispelTint and sc.dispelTint.glowFrequency) or defDispel.glowFrequency or 0.25,
				dispelTintGlowX = (sc.dispelTint and sc.dispelTint.glowX) or defDispel.glowX or 0,
				dispelTintGlowY = (sc.dispelTint and sc.dispelTint.glowY) or defDispel.glowY or 0,
				dispelTintGlowLines = (sc.dispelTint and sc.dispelTint.glowLines) or defDispel.glowLines or 8,
				dispelTintGlowThickness = (sc.dispelTint and sc.dispelTint.glowThickness) or defDispel.glowThickness or 3,
				raidIconEnabled = (sc.raidIcon and sc.raidIcon.enabled) ~= false,
				raidIconSize = (sc.raidIcon and sc.raidIcon.size) or 18,
				raidIconPoint = (sc.raidIcon and sc.raidIcon.point) or "TOP",
				raidIconOffsetX = (sc.raidIcon and sc.raidIcon.x) or 0,
				raidIconOffsetY = (sc.raidIcon and sc.raidIcon.y) or -2,
				raidAssignmentIconEnabled = (sc.raidAssignmentIcon and sc.raidAssignmentIcon.enabled) == true,
				raidAssignmentIconSize = (sc.raidAssignmentIcon and sc.raidAssignmentIcon.size) or ((def.status and def.status.raidAssignmentIcon and def.status.raidAssignmentIcon.size) or 12),
				raidAssignmentIconPoint = (sc.raidAssignmentIcon and sc.raidAssignmentIcon.point)
					or ((def.status and def.status.raidAssignmentIcon and def.status.raidAssignmentIcon.point) or "TOPRIGHT"),
				raidAssignmentIconOffsetX = (sc.raidAssignmentIcon and sc.raidAssignmentIcon.x) or ((def.status and def.status.raidAssignmentIcon and def.status.raidAssignmentIcon.x) or -2),
				raidAssignmentIconOffsetY = (sc.raidAssignmentIcon and sc.raidAssignmentIcon.y) or ((def.status and def.status.raidAssignmentIcon and def.status.raidAssignmentIcon.y) or -2),
				leaderIconEnabled = lc.enabled ~= false,
				leaderIconSize = lc.size or 12,
				leaderIconPoint = lc.point or "TOPLEFT",
				leaderIconOffsetX = lc.x or 0,
				leaderIconOffsetY = lc.y or 0,
				assistIconEnabled = acfg.enabled ~= false,
				assistIconSize = acfg.size or 12,
				assistIconPoint = acfg.point or "TOPLEFT",
				assistIconOffsetX = acfg.x or 0,
				assistIconOffsetY = acfg.y or 0,
				readyCheckIconEnabled = (sc.readyCheckIcon and sc.readyCheckIcon.enabled) ~= false,
				readyCheckIconSample = (sc.readyCheckIcon and sc.readyCheckIcon.sample) == true,
				readyCheckIconSize = (sc.readyCheckIcon and sc.readyCheckIcon.size) or (def.status and def.status.readyCheckIcon and def.status.readyCheckIcon.size) or 16,
				readyCheckIconPoint = (sc.readyCheckIcon and sc.readyCheckIcon.point) or (def.status and def.status.readyCheckIcon and def.status.readyCheckIcon.point) or "CENTER",
				readyCheckIconRelativePoint = (sc.readyCheckIcon and sc.readyCheckIcon.relativePoint)
					or (sc.readyCheckIcon and sc.readyCheckIcon.point)
					or (def.status and def.status.readyCheckIcon and def.status.readyCheckIcon.relativePoint)
					or (def.status and def.status.readyCheckIcon and def.status.readyCheckIcon.point)
					or "CENTER",
				readyCheckIconOffsetX = (sc.readyCheckIcon and sc.readyCheckIcon.x) or (def.status and def.status.readyCheckIcon and def.status.readyCheckIcon.x) or 0,
				readyCheckIconOffsetY = (sc.readyCheckIcon and sc.readyCheckIcon.y) or (def.status and def.status.readyCheckIcon and def.status.readyCheckIcon.y) or 0,
				summonIconEnabled = (sc.summonIcon and sc.summonIcon.enabled) ~= false,
				summonIconSample = (sc.summonIcon and sc.summonIcon.sample) == true,
				summonIconSize = (sc.summonIcon and sc.summonIcon.size) or (def.status and def.status.summonIcon and def.status.summonIcon.size) or 16,
				summonIconPoint = (sc.summonIcon and sc.summonIcon.point) or (def.status and def.status.summonIcon and def.status.summonIcon.point) or "CENTER",
				summonIconRelativePoint = (sc.summonIcon and sc.summonIcon.relativePoint)
					or (sc.summonIcon and sc.summonIcon.point)
					or (def.status and def.status.summonIcon and def.status.summonIcon.relativePoint)
					or (def.status and def.status.summonIcon and def.status.summonIcon.point)
					or "CENTER",
				summonIconOffsetX = (sc.summonIcon and sc.summonIcon.x) or (def.status and def.status.summonIcon and def.status.summonIcon.x) or 0,
				summonIconOffsetY = (sc.summonIcon and sc.summonIcon.y) or (def.status and def.status.summonIcon and def.status.summonIcon.y) or 0,
				resurrectIconEnabled = (sc.resurrectIcon and sc.resurrectIcon.enabled) ~= false,
				resurrectIconSample = (sc.resurrectIcon and sc.resurrectIcon.sample) == true,
				resurrectIconSize = (sc.resurrectIcon and sc.resurrectIcon.size) or (def.status and def.status.resurrectIcon and def.status.resurrectIcon.size) or 16,
				resurrectIconPoint = (sc.resurrectIcon and sc.resurrectIcon.point) or (def.status and def.status.resurrectIcon and def.status.resurrectIcon.point) or "CENTER",
				resurrectIconRelativePoint = (sc.resurrectIcon and sc.resurrectIcon.relativePoint)
					or (sc.resurrectIcon and sc.resurrectIcon.point)
					or (def.status and def.status.resurrectIcon and def.status.resurrectIcon.relativePoint)
					or (def.status and def.status.resurrectIcon and def.status.resurrectIcon.point)
					or "CENTER",
				resurrectIconOffsetX = (sc.resurrectIcon and sc.resurrectIcon.x) or (def.status and def.status.resurrectIcon and def.status.resurrectIcon.x) or 0,
				resurrectIconOffsetY = (sc.resurrectIcon and sc.resurrectIcon.y) or (def.status and def.status.resurrectIcon and def.status.resurrectIcon.y) or 0,
				phaseIconEnabled = (sc.phaseIcon and sc.phaseIcon.enabled) == true,
				phaseIconSample = (sc.phaseIcon and sc.phaseIcon.sample) == true,
				phaseIconSize = (sc.phaseIcon and sc.phaseIcon.size) or (def.status and def.status.phaseIcon and def.status.phaseIcon.size) or 14,
				phaseIconPoint = (sc.phaseIcon and sc.phaseIcon.point) or (def.status and def.status.phaseIcon and def.status.phaseIcon.point) or "TOPLEFT",
				phaseIconRelativePoint = (sc.phaseIcon and sc.phaseIcon.relativePoint)
					or (sc.phaseIcon and sc.phaseIcon.point)
					or (def.status and def.status.phaseIcon and def.status.phaseIcon.relativePoint)
					or (def.status and def.status.phaseIcon and def.status.phaseIcon.point)
					or "TOPLEFT",
				phaseIconOffsetX = (sc.phaseIcon and sc.phaseIcon.x) or (def.status and def.status.phaseIcon and def.status.phaseIcon.x) or 0,
				phaseIconOffsetY = (sc.phaseIcon and sc.phaseIcon.y) or (def.status and def.status.phaseIcon and def.status.phaseIcon.y) or 0,
				roleIconEnabled = rc.enabled ~= false,
				roleIconSize = rc.size or 14,
				roleIconPoint = rc.point or "LEFT",
				roleIconOffsetX = rc.x or 0,
				roleIconOffsetY = rc.y or 0,
				roleIconStyle = rc.style or "TINY",
				roleIconRoles = (type(rc.showRoles) == "table") and GF._sharedEdit.csm(rc.showRoles) or GF._sharedEdit.defaultRoleSel(),
				powerRoles = (type(pcfg.showRoles) == "table") and GF._sharedEdit.csm(pcfg.showRoles) or GF._sharedEdit.defaultRoleSel(),
				powerSpecs = (type(pcfg.showSpecs) == "table") and GF._sharedEdit.csm(pcfg.showSpecs) or GF._sharedEdit.defaultSpecSel(),
				powerDetached = GF.IsGroupPowerDetached(cfg, kind),
				powerDetachedMatchHealthWidth = pcfg.detachedMatchHealthWidth ~= false,
				powerDetachedWidth = pcfg.width or cfg.width or (DEFAULTS[kind] and DEFAULTS[kind].width) or 100,
				powerDetachedHeight = pcfg.detachedHeight or cfg.powerHeight or (DEFAULTS[kind] and DEFAULTS[kind].powerHeight) or 6,
				powerDetachedBorder = pcfg.detachedBorder == true,
				powerDetachedBorderTexture = pcfg.detachedBorderTexture or bc.texture or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.texture) or "DEFAULT",
				powerDetachedBorderSize = pcfg.detachedBorderSize or bc.edgeSize or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.edgeSize) or 1,
				powerDetachedBorderOffset = pcfg.detachedBorderOffset or bc.offset or (DEFAULTS[kind] and DEFAULTS[kind].border and DEFAULTS[kind].border.offset) or 1,
				powerDetachedStrata = GF.NormalizeFrameStrataToken(pcfg.detachedStrata) or "",
				powerDetachedFrameLevelOffset = pcfg.detachedFrameLevelOffset or (DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.detachedFrameLevelOffset) or 2,
				powerDetachedGrowFromCenter = pcfg.detachedGrowFromCenter == true,
				powerDetachedOffsetX = (pcfg.offset and pcfg.offset.x) or 0,
				powerDetachedOffsetY = (pcfg.offset and pcfg.offset.y) or 0,
				powerTextLeft = pcfg.textLeft or ((DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textLeft) or "NONE"),
				powerTextCenter = pcfg.textCenter or ((DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textCenter) or "NONE"),
				powerTextRight = pcfg.textRight or ((DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textRight) or "NONE"),
				powerDelimiter = pcfg.textDelimiter or ((DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiter) or " "),
				powerDelimiterSecondary = pcfg.textDelimiterSecondary or ((DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterSecondary) or (pcfg.textDelimiter or " ")),
				powerDelimiterTertiary = pcfg.textDelimiterTertiary
					or ((DEFAULTS[kind] and DEFAULTS[kind].power and DEFAULTS[kind].power.textDelimiterTertiary) or (pcfg.textDelimiterSecondary or pcfg.textDelimiter or " ")),
				powerShortNumbers = pcfg.useShortNumbers ~= false,
				powerRoundPercent = pcfg.roundPercent == true,
				powerHidePercent = pcfg.hidePercentSymbol == true,
				powerFontSize = pcfg.fontSize or defP.fontSize or 10,
				powerFont = pcfg.font or defP.font or nil,
				powerFontOutline = pcfg.fontOutline or defP.fontOutline or "OUTLINE",
				powerTexture = pcfg.texture or defP.texture or "DEFAULT",
				powerSmoothFill = (pcfg.smoothFill ~= nil) and (pcfg.smoothFill == true) or (defP.smoothFill == true),
				powerBackdropEnabled = (pcfgBackdrop.enabled ~= nil) and (pcfgBackdrop.enabled ~= false) or (defPBackdrop.enabled ~= false),
				powerBackdropColor = pcfgBackdrop.color or defPBackdrop.color or { 0, 0, 0, 0.6 },
				powerBackdropTexture = pcfgBackdrop.texture or defPBackdrop.texture or "DEFAULT",
				powerLeftX = (pcfg.offsetLeft and pcfg.offsetLeft.x) or 0,
				powerLeftY = (pcfg.offsetLeft and pcfg.offsetLeft.y) or 0,
				powerCenterX = (pcfg.offsetCenter and pcfg.offsetCenter.x) or 0,
				powerCenterY = (pcfg.offsetCenter and pcfg.offsetCenter.y) or 0,
				powerRightX = (pcfg.offsetRight and pcfg.offsetRight.x) or 0,
				powerRightY = (pcfg.offsetRight and pcfg.offsetRight.y) or 0,
				auraRenderer = GF.GetGroupAuraRenderer(cfg, def),
				auraRendererBlizzardTypes = GF._sharedEdit.csm((ac and ac.blizzardTypes) or (defAuras and defAuras.blizzardTypes) or GF.BLIZZARD_AURA_RENDER_TYPES),
				auraRendererBlizzardIconSize = GF.GetBlizzardAuraIconSize(cfg, def),
				auraRendererBlizzardCooldownText = GF.IsBlizzardAuraCooldownTextEnabled(cfg, def),
				auraRendererBlizzardOrganization = GF.GetBlizzardAuraOrganization(cfg, def),
				auraRendererBlizzardDispelMode = GF.GetBlizzardDispelIndicatorMode(cfg, def),
				privateAurasEnabled = (pa.enabled ~= nil) and (pa.enabled == true) or ((pa.enabled == nil) and defPrivate.enabled == true),
				privateAurasAmount = paIcon.amount or defPrivateIcon.amount or 2,
				privateAurasSize = paIcon.size or defPrivateIcon.size or 20,
				privateAurasTextScale = tonumber(pa.textScale) or tonumber(defPrivate.textScale) or 1,
				privateAurasPoint = paIcon.point or defPrivateIcon.point or "LEFT",
				privateAurasOffset = paIcon.offset or defPrivateIcon.offset or 2,
				privateAurasParentPoint = paParent.point or defPrivateParent.point or "CENTER",
				privateAurasParentOffsetX = paParent.offsetX or defPrivateParent.offsetX or 0,
				privateAurasParentOffsetY = paParent.offsetY or defPrivateParent.offsetY or 0,
				privateAurasCountdownFrame = (pa.countdownFrame ~= nil) and (pa.countdownFrame ~= false) or ((pa.countdownFrame == nil) and defPrivate.countdownFrame ~= false),
				privateAurasCountdownNumbers = (pa.countdownNumbers ~= nil) and (pa.countdownNumbers ~= false) or ((pa.countdownNumbers == nil) and defPrivate.countdownNumbers ~= false),
				privateAurasShowTooltip = (pa.showTooltip ~= nil) and (pa.showTooltip == true) or ((pa.showTooltip == nil) and defPrivate.showTooltip == true),
				privateAurasShowDispelType = (pa.showDispelType ~= nil) and (pa.showDispelType == true) or ((pa.showDispelType == nil) and defPrivate.showDispelType == true),
				privateAurasDurationEnabled = (paDuration.enable ~= nil) and (paDuration.enable == true) or ((paDuration.enable == nil) and defPrivateDuration.enable == true),
				privateAurasDurationPoint = paDuration.point or defPrivateDuration.point or "BOTTOM",
				privateAurasDurationOffsetX = paDuration.offsetX or defPrivateDuration.offsetX or 0,
				privateAurasDurationOffsetY = paDuration.offsetY or defPrivateDuration.offsetY or 0,
				buffsEnabled = ac.buff.enabled == true,
				buffAnchor = buffAnchor,
				buffAnchorOutside = ac.buff.anchorOutside == true,
				buffGrowth = buffGrowth,
				buffOffsetX = ac.buff.x or 0,
				buffOffsetY = ac.buff.y or 0,
				buffSize = ac.buff.size or 16,
				buffPerRow = ac.buff.perRow or 6,
				buffMax = GF.ClampAuraCount(ac.buff.max, 6) or 6,
				buffSpacing = ac.buff.spacing or 2,
				buffIconShape = GF.NormalizeAuraIconShape(ac.buff.iconShape, defBuff.iconShape or "DEFAULT"),
				buffBorderTexture = GF.NormalizeAuraBorderTexture(ac.buff.borderTexture, defBuff.borderTexture or "DEFAULT", ac.buff.iconShape or defBuff.iconShape),
				buffBorderSize = ac.buff.borderSize or defBuff.borderSize or 2,
				buffBorderOffset = ac.buff.borderOffset or defBuff.borderOffset or 0,
				buffFilters = GF.ExportGroupBuffSelection(ac.buff),
				buffCooldownEnabled = (ac.buff.showCooldown ~= nil and ac.buff.showCooldown ~= false) or (ac.buff.showCooldown == nil and defBuff.showCooldown ~= false),
				buffCooldownTextEnabled = (ac.buff.showCooldownText ~= nil and ac.buff.showCooldownText ~= false) or (ac.buff.showCooldownText == nil and defBuff.showCooldownText ~= false),
				buffCooldownTextAnchor = ac.buff.cooldownAnchor or defBuff.cooldownAnchor or "CENTER",
				buffCooldownTextOffsetX = (ac.buff.cooldownOffset and ac.buff.cooldownOffset.x) or (defBuff.cooldownOffset and defBuff.cooldownOffset.x) or 0,
				buffCooldownTextOffsetY = (ac.buff.cooldownOffset and ac.buff.cooldownOffset.y) or (defBuff.cooldownOffset and defBuff.cooldownOffset.y) or 0,
				buffCooldownTextSize = ac.buff.cooldownFontSize or defBuff.cooldownFontSize or 12,
				buffCooldownTextFont = ac.buff.cooldownFont or defBuff.cooldownFont or nil,
				buffCooldownTextOutline = ac.buff.cooldownFontOutline or defBuff.cooldownFontOutline or "OUTLINE",
				buffStackTextEnabled = (ac.buff.showStacks ~= nil and ac.buff.showStacks ~= false) or (ac.buff.showStacks == nil and defBuff.showStacks ~= false),
				buffStackAnchor = ac.buff.countAnchor or defBuff.countAnchor or "BOTTOMRIGHT",
				buffStackOffsetX = (ac.buff.countOffset and ac.buff.countOffset.x) or (defBuff.countOffset and defBuff.countOffset.x) or -2,
				buffStackOffsetY = (ac.buff.countOffset and ac.buff.countOffset.y) or (defBuff.countOffset and defBuff.countOffset.y) or 2,
				buffStackSize = ac.buff.countFontSize or defBuff.countFontSize or 12,
				buffStackFont = ac.buff.countFont or defBuff.countFont or nil,
				buffStackOutline = ac.buff.countFontOutline or defBuff.countFontOutline or "OUTLINE",
				debuffsEnabled = ac.debuff.enabled == true,
				debuffFilters = exportGroupDebuffSelection(ac.debuff),
				debuffAnchor = debuffAnchor,
				debuffAnchorOutside = ac.debuff.anchorOutside == true,
				debuffGrowth = debuffGrowth,
				debuffOffsetX = ac.debuff.x or 0,
				debuffOffsetY = ac.debuff.y or 0,
				debuffSize = ac.debuff.size or 16,
				debuffPerRow = ac.debuff.perRow or 6,
				debuffMax = GF.ClampAuraCount(ac.debuff.max, 6) or 6,
				debuffDisplayLargerRoleSpecific = GF.IsBlizzardLargerRoleDebuffEnabled(cfg, def),
				debuffSpacing = ac.debuff.spacing or 2,
				debuffIconShape = GF.NormalizeAuraIconShape(ac.debuff.iconShape, defDebuff.iconShape or "DEFAULT"),
				debuffBorderTexture = GF.NormalizeAuraBorderTexture(ac.debuff.borderTexture, defDebuff.borderTexture or "DEFAULT", ac.debuff.iconShape or defDebuff.iconShape),
				debuffBorderSize = ac.debuff.borderSize or defDebuff.borderSize or 2,
				debuffBorderOffset = ac.debuff.borderOffset or defDebuff.borderOffset or 0,
				debuffShowDispelIcon = (ac.debuff.showDispelIcon ~= nil and ac.debuff.showDispelIcon ~= false) or (ac.debuff.showDispelIcon == nil and defDebuff.showDispelIcon ~= false),
				debuffCooldownEnabled = (ac.debuff.showCooldown ~= nil and ac.debuff.showCooldown ~= false) or (ac.debuff.showCooldown == nil and defDebuff.showCooldown ~= false),
				debuffCooldownTextEnabled = (ac.debuff.showCooldownText ~= nil and ac.debuff.showCooldownText ~= false) or (ac.debuff.showCooldownText == nil and defDebuff.showCooldownText ~= false),
				debuffCooldownTextAnchor = ac.debuff.cooldownAnchor or defDebuff.cooldownAnchor or "CENTER",
				debuffCooldownTextOffsetX = (ac.debuff.cooldownOffset and ac.debuff.cooldownOffset.x) or (defDebuff.cooldownOffset and defDebuff.cooldownOffset.x) or 0,
				debuffCooldownTextOffsetY = (ac.debuff.cooldownOffset and ac.debuff.cooldownOffset.y) or (defDebuff.cooldownOffset and defDebuff.cooldownOffset.y) or 0,
				debuffCooldownTextSize = ac.debuff.cooldownFontSize or defDebuff.cooldownFontSize or 12,
				debuffCooldownTextFont = ac.debuff.cooldownFont or defDebuff.cooldownFont or nil,
				debuffCooldownTextOutline = ac.debuff.cooldownFontOutline or defDebuff.cooldownFontOutline or "OUTLINE",
				debuffStackTextEnabled = (ac.debuff.showStacks ~= nil and ac.debuff.showStacks ~= false) or (ac.debuff.showStacks == nil and defDebuff.showStacks ~= false),
				debuffStackAnchor = ac.debuff.countAnchor or defDebuff.countAnchor or "BOTTOMRIGHT",
				debuffStackOffsetX = (ac.debuff.countOffset and ac.debuff.countOffset.x) or (defDebuff.countOffset and defDebuff.countOffset.x) or -2,
				debuffStackOffsetY = (ac.debuff.countOffset and ac.debuff.countOffset.y) or (defDebuff.countOffset and defDebuff.countOffset.y) or 2,
				debuffStackSize = ac.debuff.countFontSize or defDebuff.countFontSize or 12,
				debuffStackFont = ac.debuff.countFont or defDebuff.countFont or nil,
				debuffStackOutline = ac.debuff.countFontOutline or defDebuff.countFontOutline or "OUTLINE",
				externalsEnabled = ac.externals.enabled == true,
				externalAnchor = externalAnchor,
				externalAnchorOutside = ac.externals.anchorOutside == true,
				externalGrowth = externalGrowth,
				externalOffsetX = ac.externals.x or 0,
				externalOffsetY = ac.externals.y or 0,
				externalSize = ac.externals.size or 16,
				externalPerRow = ac.externals.perRow or 6,
				externalMax = GF.ClampAuraCount(ac.externals.max, 4) or 4,
				externalSpacing = ac.externals.spacing or 2,
				externalIconShape = GF.NormalizeAuraIconShape(ac.externals.iconShape, defExt.iconShape or "DEFAULT"),
				externalGlowEnabled = (ac.externals.glowEnabled ~= nil and ac.externals.glowEnabled == true) or (ac.externals.glowEnabled == nil and defExt.glowEnabled == true),
				externalGlowColor = GF.GetExternalGlowColor(ac.externals, defExt),
				externalGlowStyle = GF.NormalizeExternalGlowStyleForIconShape(ac.externals.glowStyle, defExt.glowStyle or "MARCHING_ANTS", ac.externals.iconShape or defExt.iconShape),
				externalGlowInset = GF.NormalizeExternalGlowInset(ac.externals.glowInset, defExt.glowInset or 0),
				externalBorderTexture = GF.NormalizeAuraBorderTexture(ac.externals.borderTexture, defExt.borderTexture or "DEFAULT", ac.externals.iconShape or defExt.iconShape),
				externalBorderSize = ac.externals.borderSize or defExt.borderSize or 2,
				externalBorderColor = ac.externals.borderColor or defExt.borderColor or { 1, 0.25, 0.25, 1 },
				externalBorderOffset = ac.externals.borderOffset or defExt.borderOffset or 0,
				externalCooldownEnabled = (ac.externals.showCooldown ~= nil and ac.externals.showCooldown ~= false)
					or (ac.externals.showCooldown == nil and defExt.showCooldown ~= false),
				externalCooldownTextEnabled = (ac.externals.showCooldownText ~= nil and ac.externals.showCooldownText ~= false)
					or (ac.externals.showCooldownText == nil and defExt.showCooldownText ~= false),
				externalCooldownTextAnchor = ac.externals.cooldownAnchor or defExt.cooldownAnchor or "CENTER",
				externalCooldownTextOffsetX = (ac.externals.cooldownOffset and ac.externals.cooldownOffset.x) or (defExt.cooldownOffset and defExt.cooldownOffset.x) or 0,
				externalCooldownTextOffsetY = (ac.externals.cooldownOffset and ac.externals.cooldownOffset.y) or (defExt.cooldownOffset and defExt.cooldownOffset.y) or 0,
				externalCooldownTextSize = ac.externals.cooldownFontSize or defExt.cooldownFontSize or 12,
				externalCooldownTextFont = ac.externals.cooldownFont or defExt.cooldownFont or nil,
				externalCooldownTextOutline = ac.externals.cooldownFontOutline or defExt.cooldownFontOutline or "OUTLINE",
				externalStackTextEnabled = (ac.externals.showStacks ~= nil and ac.externals.showStacks ~= false) or (ac.externals.showStacks == nil and defExt.showStacks ~= false),
				externalStackAnchor = ac.externals.countAnchor or defExt.countAnchor or "BOTTOMRIGHT",
				externalStackOffsetX = (ac.externals.countOffset and ac.externals.countOffset.x) or (defExt.countOffset and defExt.countOffset.x) or -2,
				externalStackOffsetY = (ac.externals.countOffset and ac.externals.countOffset.y) or (defExt.countOffset and defExt.countOffset.y) or 2,
				externalStackSize = ac.externals.countFontSize or defExt.countFontSize or 12,
				externalStackFont = ac.externals.countFont or defExt.countFont or nil,
				externalStackOutline = ac.externals.countFontOutline or defExt.countFontOutline or "OUTLINE",
				externalDrEnabled = ac.externals.showDR == true,
				externalDrAnchor = ac.externals.drAnchor or defExt.drAnchor or "TOPLEFT",
				externalDrOffsetX = (ac.externals.drOffset and ac.externals.drOffset.x) or (defExt.drOffset and defExt.drOffset.x) or 0,
				externalDrOffsetY = (ac.externals.drOffset and ac.externals.drOffset.y) or (defExt.drOffset and defExt.drOffset.y) or 0,
				externalDrColor = ac.externals.drColor or defExt.drColor or { 1, 1, 1, 1 },
				externalDrFontSize = ac.externals.drFontSize or defExt.drFontSize or 10,
				externalDrFont = ac.externals.drFont or defExt.drFont or nil,
				externalDrFontOutline = ac.externals.drFontOutline or defExt.drFontOutline or "OUTLINE",
			}

			EditMode:RegisterFrame(EDITMODE_IDS[kind], {
				frame = anchor,
				title = (kind == "party" and (PARTY or "Party")) or (kind == "raid" and (RAID or "Raid")) or (kind == "mt" and "Main Tank") or (kind == "ma" and "Main Assist") or tostring(kind),
				layoutDefaults = defaults,
				persistPosition = false,
				settings = buildEditModeSettings(kind, EDITMODE_IDS[kind]),
				onApply = function(_, layoutName, data)
					local token = getCfg(kind) or addon.db
					if anchor._eqolEditModeHydratedToken ~= token then
						anchor._eqolEditModeHydratedToken = token
						if GF._syncGroupEditModeLayoutData then GF._syncGroupEditModeLayoutData(kind, EDITMODE_IDS[kind], layoutName) end
						if EditMode and EditMode.EnsureLayoutData then
							local synced = EditMode:EnsureLayoutData(EDITMODE_IDS[kind], layoutName)
							if type(synced) == "table" then data = synced end
						end
					end
					if type(data) == "table" and (data.point or data.relativePoint or data.x ~= nil or data.y ~= nil) then
						applyEditModeData(kind, {
							point = data.point,
							relativePoint = data.relativePoint,
							x = data.x,
							y = data.y,
						})
					end
				end,
				onPositionChanged = function(_, _, dataOrPoint, x, y)
					local positionData
					if type(dataOrPoint) == "table" then
						positionData = {
							point = dataOrPoint.point,
							relativePoint = dataOrPoint.relativePoint,
							x = dataOrPoint.x,
							y = dataOrPoint.y,
						}
					else
						positionData = {
							point = dataOrPoint,
							relativePoint = dataOrPoint,
							x = x,
							y = y,
						}
					end

					if positionData.point or positionData.relativePoint or positionData.x ~= nil or positionData.y ~= nil then applyEditModeData(kind, positionData) end
				end,
				onEnter = function() GF:OnEnterEditMode(kind) end,
				onExit = function() GF:OnExitEditMode(kind) end,
				isEnabled = function()
					local cfg = getCfg(kind)
					if not (cfg and cfg.enabled == true) then return false end
					if isSplitRoleKind(kind) then
						local db = DB or ensureDB()
						return db and db.raid and db.raid.enabled == true
					end
					return true
				end,
				relativeTo = function() return GF.ResolveGroupAnchorTargetFrame(kind, getCfg(kind) and getCfg(kind).relativeTo) end,
				allowDrag = function() return anchorUsesUIParent(kind) end,
				showOutsideEditMode = false,
				showReset = false,
				showSettingsReset = false,
				enableOverlayToggle = true,
				collapseExclusive = true,
				settingsMaxHeight = 700,
			})

			if EditMode and EditMode.RegisterButtons then
				local buttons = {
					{
						text = L["Toggle sample frames"] or "Toggle sample frames",
						layout = "compact",
						click = function() GF:ToggleEditModeSampleFrames(kind) end,
					},
					{
						text = L["Toggle sample auras"] or "Toggle sample auras",
						layout = "compact",
						click = function() GF:ToggleEditModeSampleAuras() end,
					},
					{
						text = L["Toggle status text"] or "Toggle status text",
						layout = "compact",
						click = function() GF:ToggleEditModeStatusText() end,
					},
				}
				if kind == "raid" or kind == "party" then
					table.insert(buttons, 1, {
						text = L["Edit custom sort order"] or "Edit custom sort order",
						layout = "compact",
						click = function() GF:ToggleCustomSortEditor(kind) end,
					})
				end
				if kind == "raid" or kind == "party" then
					table.insert(buttons, 2, {
						text = L["UFGroupHealerBuffEditModeButton"] or "Edit healer buff placement",
						layout = "compact",
						click = function() GF:ToggleHealerBuffPlacementEditor(kind) end,
					})
					table.insert(buttons, 3, {
						text = L["UFGroupGlobalAuraIgnoreEditModeButton"] or "Edit global aura ignore",
						layout = "compact",
						click = function() GF:ToggleGlobalAuraIgnoreEditor(kind) end,
					})
				end
				if kind == "raid" then
					table.insert(buttons, 2, {
						text = L["Cycle sample size (10/20/30/40)"] or "Cycle sample size (10/20/30/40)",
						layout = "compact",
						click = function() GF:CycleEditModeSampleSize(kind) end,
					})
				end
				EditMode:RegisterButtons(EDITMODE_IDS[kind], buttons)
			end

			if addon.EditModeLib and addon.EditModeLib.SetFrameResetVisible then addon.EditModeLib:SetFrameResetVisible(anchor, false) end
		end
	end

	GF._editModeRegistered = true
	if addon.EditModeLib and addon.EditModeLib.internal and addon.EditModeLib.internal.RefreshSettingValues then addon.EditModeLib.internal:RefreshSettingValues() end
end

function GF:OnEnterEditMode(kind)
	if not isFeatureEnabled() then return end
	GF._editModeExiting = nil
	local cfg = getCfg(kind)
	if not (cfg and cfg.enabled == true) then return end
	if GF._editModeSampleAuras == nil then GF._editModeSampleAuras = false end
	if GF._editModeSampleStatusText == nil then GF._editModeSampleStatusText = true end
	if GF._editModeSampleFrames == nil then GF._editModeSampleFrames = { party = false, raid = false, mt = false, ma = false } end
	if GF._previewSampleSize == nil then GF._previewSampleSize = { raid = 10 } end
	GF:EnsureHeaders()
	local header = GF.headers and GF.headers[kind]
	if not header then return end
	local wantSamples = GF._editModeSampleFrames and GF._editModeSampleFrames[kind] == true
	if wantSamples and not (InCombatLockdown and InCombatLockdown()) then
		header._eqolForceShow = nil
		header._eqolForceHide = true
		GF._previewActive = GF._previewActive or {}
		GF._previewActive[kind] = true
		GF:EnsurePreviewFrames(kind)
		GF:UpdatePreviewLayout(kind)
		GF:ShowPreviewFrames(kind, true)
	else
		if GF._previewActive then GF._previewActive[kind] = nil end
		GF:ShowPreviewFrames(kind, false)
		header._eqolForceHide = nil
		header._eqolForceShow = true
	end
	GF:ApplyHeaderAttributes(kind)
end

function GF:OnExitEditMode(kind)
	local editor = UF and UF.GroupFramesHealerBuffEditor
	if editor and editor.IsShown and editor:IsShown() then editor:Hide() end
	local globalIgnoreEditor = UF and UF.GlobalAuraIgnore
	if globalIgnoreEditor and globalIgnoreEditor.HideEditor then globalIgnoreEditor:HideEditor() end
	GF._editModeExiting = true
	GF:ClearEditModeAuraSamples()
	if not isFeatureEnabled() then
		GF._editModeExiting = nil
		GF.ShowEditModeReloadIfRequired()
		return
	end
	local cfg = getCfg(kind)
	if not (cfg and cfg.enabled == true) then
		GF._editModeExiting = nil
		GF.ShowEditModeReloadIfRequired()
		return
	end
	GF:EnsureHeaders()
	local header = GF.headers and GF.headers[kind]
	if not header then
		GF._editModeExiting = nil
		GF.ShowEditModeReloadIfRequired()
		return
	end
	if GF._previewActive then GF._previewActive[kind] = nil end
	GF:ShowPreviewFrames(kind, false)
	header._eqolForceHide = nil
	header._eqolForceShow = nil
	if GF._customSortEditor and GF._customSortEditor.IsShown and GF._customSortEditor:IsShown() then GF._customSortEditor:Hide() end
	GF:ApplyHeaderAttributes(kind)
	local function refreshAfterEditMode()
		GF:ClearEditModeAuraSamples()
		if isEditModeActive() then
			if C_Timer and C_Timer.After then C_Timer.After(0.05, refreshAfterEditMode) end
			return
		end
		GF._editModeExiting = nil
		refreshAllAuras()
		refreshAllPrivateAuras()
		GF.ShowEditModeReloadIfRequired()
	end
	RunNextFrame(refreshAfterEditMode)
end

registerFeatureEvents = function(frame)
	if not frame then return end
	if frame.RegisterEvent then
		frame:RegisterEvent("PLAYER_ENTERING_WORLD")
		frame:RegisterEvent("PLAYER_REGEN_ENABLED")
		frame:RegisterEvent("PLAYER_FLAGS_CHANGED")
		frame:RegisterEvent("UNIT_CONNECTION")
		frame:RegisterEvent("PARTY_MEMBER_ENABLE")
		frame:RegisterEvent("PARTY_MEMBER_DISABLE")
		frame:RegisterEvent("GROUP_FORMED")
		frame:RegisterEvent("GROUP_JOINED")
		frame:RegisterEvent("GROUP_ROSTER_UPDATE")
		frame:RegisterEvent("RAID_ROSTER_UPDATE")
		frame:RegisterEvent("UNIT_NAME_UPDATE")
		frame:RegisterEvent("PARTY_LEADER_CHANGED")
		frame:RegisterEvent("PLAYER_ROLES_ASSIGNED")
		frame:RegisterEvent("ACTIVE_PLAYER_SPECIALIZATION_CHANGED")
		if frame.RegisterUnitEvent then
			frame:RegisterUnitEvent("PLAYER_SPECIALIZATION_CHANGED", "player")
		else
			frame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
		end
		frame:RegisterEvent("PLAYER_TALENT_UPDATE")
		frame:RegisterEvent("TRAIT_CONFIG_UPDATED")
		frame:RegisterEvent("SPELLS_CHANGED")
		frame:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
		frame:RegisterEvent("INSPECT_READY")
		frame:RegisterEvent("RAID_TARGET_UPDATE")
		frame:RegisterEvent("PLAYER_TARGET_CHANGED")
		frame:RegisterEvent("READY_CHECK")
		frame:RegisterEvent("READY_CHECK_CONFIRM")
		frame:RegisterEvent("READY_CHECK_FINISHED")
	end
end

unregisterFeatureEvents = function(frame)
	if not frame then return end
	if frame.UnregisterEvent then
		frame:UnregisterEvent("PLAYER_ENTERING_WORLD")
		frame:UnregisterEvent("PLAYER_REGEN_ENABLED")
		frame:UnregisterEvent("PLAYER_FLAGS_CHANGED")
		frame:UnregisterEvent("UNIT_CONNECTION")
		frame:UnregisterEvent("PARTY_MEMBER_ENABLE")
		frame:UnregisterEvent("PARTY_MEMBER_DISABLE")
		frame:UnregisterEvent("GROUP_FORMED")
		frame:UnregisterEvent("GROUP_JOINED")
		frame:UnregisterEvent("GROUP_ROSTER_UPDATE")
		frame:UnregisterEvent("RAID_ROSTER_UPDATE")
		frame:UnregisterEvent("UNIT_NAME_UPDATE")
		frame:UnregisterEvent("PARTY_LEADER_CHANGED")
		frame:UnregisterEvent("PLAYER_ROLES_ASSIGNED")
		frame:UnregisterEvent("ACTIVE_PLAYER_SPECIALIZATION_CHANGED")
		frame:UnregisterEvent("PLAYER_SPECIALIZATION_CHANGED")
		frame:UnregisterEvent("PLAYER_TALENT_UPDATE")
		frame:UnregisterEvent("TRAIT_CONFIG_UPDATED")
		frame:UnregisterEvent("SPELLS_CHANGED")
		frame:UnregisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
		frame:UnregisterEvent("INSPECT_READY")
		frame:UnregisterEvent("RAID_TARGET_UPDATE")
		frame:UnregisterEvent("PLAYER_TARGET_CHANGED")
		frame:UnregisterEvent("READY_CHECK")
		frame:UnregisterEvent("READY_CHECK_CONFIRM")
		frame:UnregisterEvent("READY_CHECK_FINISHED")
	end
end

function GF:CancelPostEnterWorldRefreshTicker()
	local ticker = self._postEnterWorldTicker
	if ticker and ticker.Cancel then ticker:Cancel() end
	self._postEnterWorldTicker = nil
	self._postEnterWorldRefreshPasses = nil
end

function GF:CancelPostRosterRefreshTicker()
	local ticker = self._postRosterRefreshTicker
	if ticker and ticker.Cancel then ticker:Cancel() end
	self._postRosterRefreshTicker = nil
	self._postRosterRefreshPasses = nil
end

function GF:RunProfileChangeRefreshPass()
	if not isFeatureEnabled() then return end
	self:EnsureHeaders()
	self.SyncAllGroupEditModeLayoutData()
	-- Profile switches can flip secure header attributes like showPlayer while
	-- the group header is live. Use the lighter refresh path so the secure
	-- header owns child rebuilding unless the layout key actually changed.
	self.Refresh()
	-- self.FullRefresh()
	self:RefreshRangeFade()
	self:RefreshRoleIcons()
	self:RefreshNames()
	self:RefreshGroupIcons()
	self:RefreshStatusIcons()
	self:RefreshStatusText()
	self:RefreshGroupIndicators()
	queueGroupIndicatorRefresh(0.05, 4)
end

function GF:RunPostRosterRefreshPass()
	if not isFeatureEnabled() then return end
	self:EnsureHeaders()
	local appliedHeaders = false
	if InCombatLockdown and InCombatLockdown() then
		if self._postRosterHeaderApplyPending then
			GF:MarkPendingHeaderRefresh("party")
			GF:MarkPendingHeaderRefresh("raid")
			GF:MarkPendingHeaderRefresh("mt")
			GF:MarkPendingHeaderRefresh("ma")
		end
		return
	end
	if self._postRosterHeaderApplyPending then
		self._postRosterHeaderApplyPending = false
		appliedHeaders = true
		local options = GF._lightHeaderRefreshOptions
		self:ApplyHeaderAttributes("party", options)
		self:ApplyHeaderAttributes("raid", options)
		self:ApplyHeaderAttributes("mt", options)
		self:ApplyHeaderAttributes("ma", options)
	else
		local options = GF._lightHeaderRefreshOptions
		if self:IsCustomRosterSortActive("party") then
			self:ApplyHeaderAttributes("party", options)
			appliedHeaders = true
		end
		if self:IsCustomRosterSortActive("raid") then
			self:ApplyHeaderAttributes("raid", options)
			appliedHeaders = true
		end

		local function nudgeVisible(header)
			if not (header and header.IsShown and header:IsShown()) then return end
			nudgeHeaderLayout(header)
		end

		nudgeVisible(self.headers and self.headers.party)
		nudgeVisible(self.headers and self.headers.raid)
		nudgeVisible(self.headers and self.headers.mt)
		nudgeVisible(self.headers and self.headers.ma)
		if self._raidGroupHeaders then
			for _, header in ipairs(self._raidGroupHeaders) do
				if header and not header._eqolSpecialHide then nudgeVisible(header) end
			end
		end
	end
	local updated = self:RefreshChangedUnitButtons() or 0
	if appliedHeaders or updated > 0 then
		self:RefreshNames({ skipLayout = true })
		self:RefreshGroupIndicators()
	end
end

function GF:RunPostEnterWorldRefreshPass()
	if not isFeatureEnabled() then return end
	self:EnsureHeaders()
	local fullRepair = self._postEnterWorldDidFullPass ~= true
	self._postEnterWorldDidFullPass = true
	local updated = fullRepair and self.FullRefresh() or self.Refresh()
	if fullRepair or (updated or 0) > 0 then
		self:RefreshRangeFade()
		self:RefreshRoleIcons()
		self:RefreshNames({ skipLayout = true })
		self:RefreshGroupIcons()
		self:RefreshStatusIcons()
		self:RefreshStatusText()
		self:RefreshGroupIndicators()
		if fullRepair then queueGroupIndicatorRefresh(0.05, 2) end
	else
		self:RefreshConnectionStateIfChanged()
	end
end

function GF:SchedulePostRosterRefresh()
	self:CancelPostRosterRefreshTicker()
	if not (C_Timer and C_Timer.NewTicker) then
		self:RunPostRosterRefreshPass()
		return
	end
	self._postRosterRefreshPasses = 0
	self._postRosterRefreshTicker = C_Timer.NewTicker(0.12, function()
		GF._postRosterRefreshPasses = (GF._postRosterRefreshPasses or 0) + 1
		if not isFeatureEnabled() then
			GF:CancelPostRosterRefreshTicker()
			return
		end
		GF:RunPostRosterRefreshPass()
		if (GF._postRosterRefreshPasses or 0) >= 4 then GF:CancelPostRosterRefreshTicker() end
	end)
end

function GF:SchedulePostEnterWorldRefresh()
	self:CancelPostEnterWorldRefreshTicker()
	if not (C_Timer and C_Timer.NewTicker) then
		self:RunPostEnterWorldRefreshPass()
		return
	end
	self._postEnterWorldRefreshPasses = 0
	self._postEnterWorldTicker = C_Timer.NewTicker(0.25, function()
		GF._postEnterWorldRefreshPasses = (GF._postEnterWorldRefreshPasses or 0) + 1
		if not isFeatureEnabled() then
			GF:CancelPostEnterWorldRefreshTicker()
			return
		end
		if not (InCombatLockdown and InCombatLockdown()) then GF:RunPostEnterWorldRefreshPass() end
		if (GF._postEnterWorldRefreshPasses or 0) >= 4 then GF:CancelPostEnterWorldRefreshTicker() end
	end)
end

do
	GF._eventFrame = CreateFrame("Frame")
	GF._eventFrame:RegisterEvent("PLAYER_LOGIN")
	GF._eventFrame:RegisterEvent("CLIENT_SCENE_OPENED")
	GF._eventFrame:RegisterEvent("CLIENT_SCENE_CLOSED")
	GF._eventFrame:SetScript("OnEvent", function(_, event, ...)
		if event == "PLAYER_LOGIN" then
			if isFeatureEnabled() then
				registerFeatureEvents(_)
				GF:EnsureHeaders()
				GF.FullRefresh()
				GF:DisableBlizzardFrames()
				GF:EnsureEditMode()
			end
		elseif event == "PLAYER_ENTERING_WORLD" then
			GF._postEnterWorldDidFullPass = nil
			GF:RunPostEnterWorldRefreshPass()
			GF:SchedulePostEnterWorldRefresh()
		elseif event == "PLAYER_REGEN_ENABLED" then
			if GF._pendingDisable then
				GF._pendingDisable = nil
				GF:DisableFeature()
			elseif GF._pendingBlizzardDisable then
				GF:DisableBlizzardFrames()
			elseif GF._pendingRefresh or (GF._pendingSortKinds and next(GF._pendingSortKinds)) then
				GF._pendingRefresh = false
				local applied = GF:ApplyPendingHeaderKinds()
				local appliedSort = GF:ApplyPendingSortKinds()
				if not applied and not appliedSort then GF.FullRefresh() end
			end
		elseif event == "CLIENT_SCENE_OPENED" then
			local sceneType = ...
			GF._clientSceneActive = (sceneType == 1)
			if isFeatureEnabled() then GF:RefreshClientSceneVisibility() end
		elseif event == "CLIENT_SCENE_CLOSED" then
			GF._clientSceneActive = false
			if isFeatureEnabled() then GF:RefreshClientSceneVisibility() end
		elseif not isFeatureEnabled() then
			return
		elseif event == "RAID_TARGET_UPDATE" then
			GF:RefreshRaidIcons()
		elseif event == "READY_CHECK" or event == "READY_CHECK_CONFIRM" or event == "READY_CHECK_FINISHED" then
			GF:RefreshReadyCheckIcons(event)
		elseif event == "PLAYER_TARGET_CHANGED" then
			GF:RefreshTargetHighlights()
		elseif event == "UNIT_CONNECTION" or event == "PARTY_MEMBER_ENABLE" or event == "PARTY_MEMBER_DISABLE" then
			local unit = ...
			GF:RefreshConnectionState(unit)
			GF:ScheduleConnectionRefresh()
			GF:ScheduleConnectionRecheck(unit)
		elseif event == "PLAYER_FLAGS_CHANGED" then
			local refreshed = GF:RefreshConnectionState(...)
			if refreshed == 0 then GF:RefreshStatusText() end
			elseif event == "INSPECT_READY" then
				if GFH and GFH.OnInspectReady then
					local updated = GFH.OnInspectReady(...)
					if updated then
						GF:RefreshCustomSortNameList("raid")
						if GF._previewActive and GF._previewActive.raid then GF:UpdatePreviewLayout("raid") end
					end
				end
			elseif event == "GROUP_FORMED" or event == "GROUP_JOINED" then
				if GF:HasCustomRosterSort() then GF:SchedulePostRosterRefresh() end
			elseif event == "GROUP_ROSTER_UPDATE" then
				local headerStateChanged = GF:DidRosterStateChange()
			local cfg = getCfg("raid")
			local custom = cfg and GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
			local sortMethod = cfg and resolveSortMethod(cfg) or "INDEX"
			local updatedCount = 0
			if headerStateChanged then
				if InCombatLockdown and InCombatLockdown() then
					GF._postRosterHeaderApplyPending = true
					GF:MarkPendingHeaderRefresh("party")
					GF:MarkPendingHeaderRefresh("raid")
					GF:MarkPendingHeaderRefresh("mt")
					GF:MarkPendingHeaderRefresh("ma")
				else
					local options = GF._lightHeaderRefreshOptions
					GF:ApplyHeaderAttributes("party", options)
					GF:ApplyHeaderAttributes("raid", options)
					GF:ApplyHeaderAttributes("mt", options)
					GF:ApplyHeaderAttributes("ma", options)
				end
			end
			updatedCount = updatedCount + (GF:RefreshChangedUnitButtons() or 0)
			GF:RefreshConnectionStateIfChanged()
			GF:RefreshGroupIcons()
			if headerStateChanged or updatedCount > 0 then
				GF:RefreshStatusIcons()
				GF:RefreshGroupIndicators()
				queueGroupIndicatorRefresh(0, 4)
			end
			GF:ScheduleConnectionRefresh()
			if headerStateChanged or updatedCount > 0 then GF:SchedulePostRosterRefresh() end
			if custom and custom.separateMeleeRanged == true and sortMethod == "NAMELIST" and GFH and GFH.QueueInspectGroup then GFH.QueueInspectGroup() end
		elseif event == "RAID_ROSTER_UPDATE" then
			local headerStateChanged = GF:DidRosterStateChange()
			if headerStateChanged then
				if InCombatLockdown and InCombatLockdown() then
					GF._postRosterHeaderApplyPending = true
					GF:MarkPendingHeaderRefresh("party")
					GF:MarkPendingHeaderRefresh("raid")
					GF:MarkPendingHeaderRefresh("mt")
					GF:MarkPendingHeaderRefresh("ma")
				else
					local options = GF._lightHeaderRefreshOptions
					GF:ApplyHeaderAttributes("party", options)
					GF:ApplyHeaderAttributes("raid", options)
					GF:ApplyHeaderAttributes("mt", options)
					GF:ApplyHeaderAttributes("ma", options)
				end
			end
			GF:RefreshConnectionStateIfChanged()
			GF:RefreshGroupIcons()
			GF:RefreshStatusIcons()
			GF:ScheduleConnectionRefresh()
			if headerStateChanged then GF:SchedulePostRosterRefresh() end
		elseif event == "PLAYER_ROLES_ASSIGNED" then
			GF:RefreshRoleIcons()
			GF:RefreshTargetHighlights()
			GF:RefreshSplitRoleHeadersForViewerRoleChange()
			GF:RefreshCustomSortNameList("raid")
			GF:RefreshCustomSortNameList("party")
			local cfg = getCfg("raid")
			local custom = cfg and GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
			if custom and custom.separateMeleeRanged == true and resolveSortMethod(cfg) == "NAMELIST" and GFH and GFH.QueueInspectGroup then GFH.QueueInspectGroup() end
		elseif event == "PARTY_LEADER_CHANGED" then
			GF:RefreshGroupIcons()
			GF:RefreshStatusText()
		elseif event == "UNIT_NAME_UPDATE" then
			GF:RefreshGroupIndicators()
		elseif event == "PLAYER_SPECIALIZATION_CHANGED" or event == "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" then
			local unit = ...
			if event == "PLAYER_SPECIALIZATION_CHANGED" and unit and unit ~= "player" then return end
			if not IsInGroup() then
				GF:RefreshRoleIcons()
				GF:ScheduleRoleIconRefresh()
			end
			GF:RefreshPowerVisibility()
			GF:RefreshSplitRoleHeadersForViewerRoleChange()
			GF:RefreshCustomSortNameList("raid")
			GF:RefreshCustomSortNameList("party")
			local cfg = getCfg("raid")
			local custom = cfg and GFH and GFH.EnsureCustomSortConfig and GFH.EnsureCustomSortConfig(cfg)
			if custom and custom.separateMeleeRanged == true and resolveSortMethod(cfg) == "NAMELIST" and GFH and GFH.QueueInspectGroup then GFH.QueueInspectGroup() end
			refreshAllAuras()
		elseif event == "PLAYER_TALENT_UPDATE" or event == "TRAIT_CONFIG_UPDATED" or event == "SPELLS_CHANGED" or event == "ACTIVE_TALENT_GROUP_CHANGED" then
			refreshAllAuras()
		end
	end)
end
