local _, ns = ...
local SlickUI = ns.SlickUI
local Theme = SlickUI.Theme
local oUF = ns.oUF

local UsesManagedAuraContainers = select(4, GetBuildInfo()) >= 120100
if UsesManagedAuraContainers and C_AddOns and not C_AddOns.IsAddOnLoaded("Blizzard_AuraContainer") then
	pcall(C_AddOns.LoadAddOn, "Blizzard_AuraContainer")
end
UsesManagedAuraContainers = UsesManagedAuraContainers
	and C_XMLUtil and C_XMLUtil.GetTemplateInfo
	and C_XMLUtil.GetTemplateInfo("CustomAuraContainerTemplate") ~= nil

local Labels = {
	player = "Player",
	target = "Target",
	focus = "Focus",
	pet = "Pet",
	targettarget = "Target of Target",
	boss = "Boss Frames",
}

SlickUI.BossFrames = SlickUI.BossFrames or {}

-- SecureUnitButtonTemplate handles right-click through the "togglemenu"
-- attribute, but it still expects the layout to provide this callback.
-- Sharing it keeps personal and group frames on Blizzard's current menu API
-- without installing an insecure OnClick handler.
function SlickUI.OpenUnitMenu(frame, menuType)
	local unit = frame.unit
	if not unit or type(UnitPopup_OpenMenu) ~= "function" then
		return
	end

	UnitPopup_OpenMenu(menuType or frame.SlickMenuType or "TARGET", {
		unit = unit,
		name = UnitName(unit),
	})
end

local function GetConfigKey(unit)
	if type(unit) == "string" and unit:match("^boss%d+$") then
		return "boss"
	end
	return unit
end

local PlayerDisplayChoices = {
	{ key = "none", label = "None" },
	{ key = "name", label = "Name", tag = "[name]" },
	{ key = "level", label = "Level", tag = "[slickui:unitlevel]" },
	{ key = "classification", label = "Classification", tag = "[slickui:classification]" },
	{ key = "status", label = "Status", tag = "[slickui:playerstatus]" },
	{ key = "healthCurrent", label = "Current Health", tag = "[slickui:shorthealth]" },
	{ key = "healthValues", label = "Current / Maximum Health", tag = "[slickui:shorthealth]/[slickui:shortmaxhealth]" },
	{ key = "healthPercent", label = "Health Percent", tag = "[perhp<$%]" },
	{ key = "healthMissing", label = "Missing Health", tag = "[slickui:shortmissinghealth]" },
	{ key = "powerCurrent", label = "Current Power", tag = "[curpp]" },
	{ key = "powerValues", label = "Current / Maximum Power", tag = "[curpp]/[maxpp]" },
	{ key = "powerPercent", label = "Power Percent", tag = "[perpp<$%]" },
}

local PlayerDisplayChoiceByKey = {}
for _, choice in ipairs(PlayerDisplayChoices) do
	PlayerDisplayChoiceByKey[choice.key] = choice
end

local PlayerDisplaySlots = {
	{ key = "topLeft", point = "TOPLEFT", relativePoint = "TOPLEFT", x = 22, y = -3, justify = "LEFT" },
	{ key = "topCenter", point = "TOP", relativePoint = "TOP", x = 0, y = -3, justify = "CENTER" },
	{ key = "topRight", point = "TOPRIGHT", relativePoint = "TOPRIGHT", x = -5, y = -3, justify = "RIGHT" },
	{ key = "middleLeft", point = "LEFT", relativePoint = "LEFT", x = 5, y = 0, justify = "LEFT" },
	{ key = "middleCenter", point = "CENTER", relativePoint = "CENTER", x = 0, y = 0, justify = "CENTER" },
	{ key = "middleRight", point = "RIGHT", relativePoint = "RIGHT", x = -5, y = 0, justify = "RIGHT" },
	{ key = "bottomLeft", point = "BOTTOMLEFT", relativePoint = "BOTTOMLEFT", x = 5, y = 3, justify = "LEFT" },
	{ key = "bottomCenter", point = "BOTTOM", relativePoint = "BOTTOM", x = 0, y = 3, justify = "CENTER" },
	{ key = "bottomRight", point = "BOTTOMRIGHT", relativePoint = "BOTTOMRIGHT", x = -5, y = 3, justify = "RIGHT" },
}

SlickUI.PlayerDisplayChoices = PlayerDisplayChoices
SlickUI.PlayerDisplayChoiceByKey = PlayerDisplayChoiceByKey
SlickUI.PlayerDisplaySlots = PlayerDisplaySlots

oUF.Tags.Methods["slickui:unitlevel"] = function(unit)
	local level = UnitLevel(unit)
	if issecretvalue and issecretvalue(level) then
		return level
	elseif type(level) == "number" and level > 0 then
		return level
	end
	return "??"
end
oUF.Tags.Events["slickui:unitlevel"] = "UNIT_LEVEL PLAYER_LEVEL_UP"

local function IsSafeBoolean(value, expected)
	if issecretvalue and issecretvalue(value) then
		return false
	end
	return value == expected
end

local function IsAttackableUnit(unit)
	if type(unit) ~= "string" or type(UnitCanAttack) ~= "function" then return false end
	local ok, attackable = pcall(UnitCanAttack, "player", unit)
	return ok and IsSafeBoolean(attackable, true)
end

oUF.Tags.Methods["slickui:playerstatus"] = function(unit)
	if IsSafeBoolean(UnitIsDead(unit), true) then
		return "Dead"
	elseif IsSafeBoolean(UnitIsGhost(unit), true) then
		return "Ghost"
	elseif IsSafeBoolean(UnitIsConnected(unit), false) then
		return "Offline"
	elseif IsSafeBoolean(UnitIsAFK(unit), true) then
		return "AFK"
	elseif IsSafeBoolean(UnitIsDND(unit), true) then
		return "DND"
	elseif unit == "player" and IsSafeBoolean(IsResting(), true) then
		return "zzz"
	end
end
oUF.Tags.Events["slickui:playerstatus"] = "UNIT_HEALTH UNIT_CONNECTION PLAYER_FLAGS_CHANGED PLAYER_UPDATE_RESTING"

local ClassificationLabels = {
	elite = "|cffffa52cElite|r",
	rare = "|cff48b8ffRare|r",
	rareelite = "|cffc76cffRare Elite|r",
	worldboss = "|cffff4040World Boss|r",
	minus = "|cff9a9a9aMinor|r",
	trivial = "|cff808080Trivial|r",
}

oUF.Tags.Methods["slickui:classification"] = function(unit)
	local classification = UnitClassification(unit)
	if canaccessvalue and not canaccessvalue(classification) then return end
	if issecretvalue and issecretvalue(classification) then return end
	if type(classification) ~= "string" then return end
	return ClassificationLabels[classification]
end
oUF.Tags.Events["slickui:classification"] = "UNIT_CLASSIFICATION_CHANGED"

local CompactNumberData = {
	breakpointData = {
		{ breakpoint = 1e9, abbreviation = "B", significandDivisor = 1e8, fractionDivisor = 10, abbreviationIsGlobal = false },
		{ breakpoint = 1e6, abbreviation = "M", significandDivisor = 1e5, fractionDivisor = 10, abbreviationIsGlobal = false },
		{ breakpoint = 1e3, abbreviation = "K", significandDivisor = 100, fractionDivisor = 10, abbreviationIsGlobal = false },
	},
}

local function CompactNumber(value)
	return AbbreviateNumbers(value, CompactNumberData)
end

oUF.Tags.Methods["slickui:shorthealth"] = function(unit)
	return CompactNumber(UnitHealth(unit))
end
oUF.Tags.Events["slickui:shorthealth"] = "UNIT_HEALTH UNIT_MAXHEALTH"

oUF.Tags.Methods["slickui:shortmaxhealth"] = function(unit)
	return CompactNumber(UnitHealthMax(unit))
end
oUF.Tags.Events["slickui:shortmaxhealth"] = "UNIT_MAXHEALTH"

oUF.Tags.Methods["slickui:shortmissinghealth"] = function(unit)
	local missing = UnitHealthMissing(unit)
	if issecretvalue and issecretvalue(missing) then return CompactNumber(missing) end
	if type(missing) ~= "number" or missing == 0 then return end
	return CompactNumber(missing)
end
oUF.Tags.Events["slickui:shortmissinghealth"] = "UNIT_HEALTH UNIT_MAXHEALTH"

local function GetSettings(unit)
	return SlickUI.DB.unitFrames.frames[GetConfigKey(unit)]
end

local function ReadableFaction(unit)
	local faction = UnitFactionGroup(unit)
	if canaccessvalue and not canaccessvalue(faction) then return nil end
	if issecretvalue and issecretvalue(faction) then return nil end
	if faction == "Alliance" or faction == "Horde" then return faction end
	return nil
end

local function UpdateOpposingFactionIndicator(frame, unit)
	local indicator = frame and frame.SlickOpposingFactionIndicator
	if not indicator then return end

	if not UnitIsPlayer(unit) then
		indicator:Hide()
		return
	end

	local playerFaction = ReadableFaction("player")
	local targetFaction = ReadableFaction(unit)
	if not playerFaction or not targetFaction or playerFaction == targetFaction then
		indicator:Hide()
		return
	end

	indicator:SetTexture("Interface\\TargetingFrame\\UI-PVP-" .. targetFaction)
	indicator:SetTexCoord(0, 0.65625, 0, 0.65625)
	indicator:Show()
end

local function UpdateUnitFrameHealthColor(health, unit)
	local frame = health:GetParent()
	UpdateOpposingFactionIndicator(frame, unit)

	if UnitIsPlayer(unit) then
		if SlickUI.DB.unitFrames.classColors then
			local _, classToken = UnitClass(unit)
			-- WoW 12.1 can return a secret class token for hostile or otherwise
			-- restricted units. Never use that value as a Lua table key: doing so
			-- raises "table cannot be indexed with secret keys" on every health
			-- refresh. Keep the existing bar color until the token is readable.
			if not classToken
				or (canaccessvalue and not canaccessvalue(classToken))
				or (issecretvalue and issecretvalue(classToken)) then
				return
			end
			local color = RAID_CLASS_COLORS[classToken]
			if color then health:SetStatusBarColor(color.r, color.g, color.b) end
		else
			health:SetStatusBarColor(unpack(Theme.Colors.health))
		end
		return
	end

	local reaction = UnitReaction(unit, "player")
	if canaccessvalue and not canaccessvalue(reaction) then return end
	if issecretvalue and issecretvalue(reaction) then return end
	if type(reaction) == "number" and reaction <= 3 then
		health:SetStatusBarColor(unpack(Theme.Colors.hostile))
	elseif reaction == 4 then
		health:SetStatusBarColor(unpack(Theme.Colors.neutral))
	end
end

local function UpdateFrameDisplayWidths(frame)
	if not frame.SlickDisplaySlots then
		return
	end

	local width = math.max(30, frame:GetWidth() / 3 - 8)
	for _, text in pairs(frame.SlickDisplaySlots) do
		text:SetWidth(width)
	end
end

local function CreateFrameDisplay(self, health, unit)
	unit = GetConfigKey(unit)
	self.SlickDisplaySlots = {}
	for _, slot in ipairs(PlayerDisplaySlots) do
		local fontSizes = SlickUI.DB.unitFrames.frameDisplayFontSizes[unit]
		local display = SlickUI.DB.unitFrames.frameDisplays[unit]
		local fontSize = fontSizes[slot.key] or 9
		local text = Theme:CreateText(health, fontSize)
		local x = slot.key == "topLeft" and unit ~= "player" and 5 or slot.x
		text:SetPoint(slot.point, health, slot.relativePoint, x, slot.y)
		text:SetJustifyH(slot.justify)
		text:SetWordWrap(false)
		self.SlickDisplaySlots[slot.key] = text

		local selected = display[slot.key] or "none"
		local choice = PlayerDisplayChoiceByKey[selected]
		if choice and choice.tag then
			self:Tag(text, choice.tag)
		else
			text:Hide()
		end
	end
	UpdateFrameDisplayWidths(self)
end

-- The solo player-frame strip mirrors the party/raid healing-effects strip.
-- Keep this list explicit: WoW 12.1 permits exact spell-ID aura queries while
-- general aura enumeration can become protected in combat.
local PlayerInformationEffectSpellIDs = {
	774, 8936, 33763, 48438, 102352, 155777, 200389, 207386, -- Druid
	17, 139, 33076, 41635, 194384, 33206, 47788, 77489,      -- Priest
	53563, 6940, 1022, 156910, 200025,                       -- Paladin
	974, 61295, 51945,                                       -- Shaman
	115175, 116849, 119611, 124682, 191840,                  -- Monk
	355941, 357170, 364343, 366155, 373267,                  -- Evoker
	20707,                                                    -- Soulstone
}

local PLAYER_INFORMATION_EFFECT_MAX_ICONS = 6

local function UpdatePlayerInformationEffects(self, event, unit)
	if event == "UNIT_AURA" and unit ~= "player" then return end
	local strip = self and self.SlickPlayerInformationEffects
	if not strip then return end

	local visible = 0
	for _, spellID in ipairs(PlayerInformationEffectSpellIDs) do
		local ok, data = pcall(C_UnitAuras.GetUnitAuraBySpellID, "player", spellID)
		if ok and data ~= nil and visible < PLAYER_INFORMATION_EFFECT_MAX_ICONS then
			visible = visible + 1
			local icon = strip.Icons[visible]
			icon.Texture:SetTexture(C_Spell.GetSpellTexture(spellID))
			-- The strip is parented to a protected unit frame. Keep its child
			-- frames shown from creation and change only texture opacity so aura
			-- updates remain safe while the player is in combat.
			icon.Texture:SetAlpha(1)
		end
	end

	for index = visible + 1, #strip.Icons do
		strip.Icons[index].Texture:SetAlpha(0)
	end
end

local function CreatePlayerInformationEffects(frame, health)
	local strip = CreateFrame("Frame", nil, frame)
	strip:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 2, -2)
	strip:SetPoint("TOPRIGHT", frame, "BOTTOMRIGHT", -2, -2)
	strip:SetHeight(15)
	strip:SetFrameLevel(frame:GetFrameLevel() + 8)
	strip:EnableMouse(false)
	local background = strip:CreateTexture(nil, "BACKGROUND")
	background:SetAllPoints()
	background:SetColorTexture(0.015, 0.018, 0.025, 0.88)
	strip.Icons = {}

	for index = 1, PLAYER_INFORMATION_EFFECT_MAX_ICONS do
		local icon = CreateFrame("Frame", nil, strip)
		icon:SetSize(13, 13)
		icon:SetPoint("LEFT", strip, "LEFT", 1 + ((index - 1) * 14), 0)
		icon:EnableMouse(false)
		local texture = icon:CreateTexture(nil, "OVERLAY")
		texture:SetAllPoints()
		texture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
		texture:SetAlpha(0)
		icon.Texture = texture
		icon:Show()
		strip.Icons[index] = icon
	end

	strip:Show()
	frame.SlickPlayerInformationEffects = strip
	frame.SlickUpdatePlayerInformationEffects = UpdatePlayerInformationEffects
	frame:RegisterEvent("UNIT_AURA", UpdatePlayerInformationEffects)
	frame:RegisterEvent("PLAYER_ENTERING_WORLD", UpdatePlayerInformationEffects, true)
end

local CastbarMoverKeys = {
	player = "castbarPlayer",
	target = "castbarTarget",
	focus = "castbarFocus",
}

local CastbarLabels = {
	player = "Player Cast Bar",
	target = "Target Cast Bar",
	focus = "Focus Cast Bar",
}

local function SetPersonalFrameInformationVisible(frame, visible)
	if not frame then return end
	local unit = frame.SlickConfigKey
	local display = unit and SlickUI.DB.unitFrames.frameDisplays[unit]
	for slotKey, fontString in pairs(frame.SlickDisplaySlots or {}) do
		local choice = display and PlayerDisplayChoiceByKey[display[slotKey] or "none"]
		fontString:SetShown(visible and choice and choice.tag ~= nil)
	end
	if not visible and frame.SlickOpposingFactionIndicator then
		frame.SlickOpposingFactionIndicator:Hide()
	elseif visible and unit then
		UpdateOpposingFactionIndicator(frame, unit)
	end
end

local function SetCastbarInformationSwap(castbar, casting)
	local frame = castbar and (castbar.__owner or castbar:GetParent())
	local unit = frame and frame.SlickConfigKey
	local settings = unit and SlickUI.DB.unitFrames.frames[unit]
	local inside = settings and settings.castbarMode == "IN_FRAME"
	SetPersonalFrameInformationVisible(frame, not (inside and casting))
end

local function LayoutCastbarContents(castbar, height, inside)
	local iconSize = inside and math.max(10, math.min(18, height - 4)) or height
	castbar.Icon:ClearAllPoints()
	castbar.Icon:SetSize(iconSize, iconSize)
	if inside then
		castbar.Icon:SetPoint("LEFT", castbar, "LEFT", 2, 0)
	else
		castbar.Icon:SetPoint("RIGHT", castbar, "LEFT", -4, 0)
	end

	castbar.Text:ClearAllPoints()
	castbar.Text:SetPoint("LEFT", inside and castbar.Icon or castbar, inside and "RIGHT" or "LEFT", inside and 4 or 5, 0)
	castbar.Text:SetPoint("RIGHT", castbar, "RIGHT", -42, 0)
	castbar.Time:ClearAllPoints()
	castbar.Time:SetPoint("RIGHT", castbar, "RIGHT", -5, 0)
end

local function CreateCastbar(self, width, unit)
	local castbar = CreateFrame("StatusBar", nil, self)
	castbar:SetStatusBarTexture(Theme.Texture)
	castbar:SetStatusBarColor(unpack(Theme.Colors.cast))
	castbar:SetSize(width, 18)
	castbar:SetPoint("TOP", self, "BOTTOM", 0, -5)
	castbar:SetFrameLevel(self:GetFrameLevel() + 5)
	Theme:CreatePanel(castbar)
	Theme:CreateBarBackground(castbar)

	local icon = castbar:CreateTexture(nil, "ARTWORK")
	icon:SetSize(18, 18)
	icon:SetPoint("RIGHT", castbar, "LEFT", -4, 0)
	icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
	castbar.Icon = icon

	local text = Theme:CreateText(castbar, 11)
	text:SetPoint("LEFT", castbar, "LEFT", 5, 0)
	text:SetPoint("RIGHT", castbar, "RIGHT", -42, 0)
	text:SetJustifyH("LEFT")
	castbar.Text = text

	local time = Theme:CreateText(castbar, 11)
	time:SetPoint("RIGHT", castbar, "RIGHT", -5, 0)
	castbar.Time = time

	local spark = castbar:CreateTexture(nil, "OVERLAY")
	spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
	spark:SetSize(10, 24)
	spark:SetBlendMode("ADD")
	castbar.Spark = spark

	local shield = castbar:CreateTexture(nil, "OVERLAY")
	shield:SetTexture("Interface\\CastingBar\\UI-CastingBar-Small-Shield")
	shield:SetSize(22, 28)
	shield:SetPoint("CENTER", icon)
	castbar.Shield = shield

	castbar.PostCastStart = function(element)
		element:SetStatusBarColor(unpack(Theme.Colors.cast))
		SetCastbarInformationSwap(element, true)
	end
	castbar.PostCastStop = function(element) SetCastbarInformationSwap(element, false) end
	castbar.PostCastFail = function(element) SetCastbarInformationSwap(element, false) end
	castbar.PostCastInterrupted = function(element) SetCastbarInformationSwap(element, false) end
	castbar:HookScript("OnHide", function(element) SetCastbarInformationSwap(element, false) end)
	castbar.PostCastInterruptible = function(element)
		local owner = element.__owner or element:GetParent()
		local unit = owner and (owner.unit or owner.SlickUnit)
		if not IsAttackableUnit(unit) then
			element:SetStatusBarColor(unpack(Theme.Colors.cast))
		elseif element.notInterruptible == true then
			element:SetStatusBarColor(unpack(Theme.Colors.castProtected))
		else
			element:SetStatusBarColor(unpack(Theme.Colors.castInterruptible))
		end
	end

	self.Castbar = castbar

	local moverKey = CastbarMoverKeys[unit]
	if moverKey then
		local holder = CreateFrame("Frame", "SlickUI_" .. moverKey, UIParent)
		holder:SetSize(width, 18)
		holder.SlickMoverOnly = true
		holder.SlickCastbarOwner = self
		holder.SlickMoverShouldShow = function()
			local owner = holder.SlickCastbarOwner
			local key = owner and owner.SlickConfigKey
			local config = key and SlickUI.DB.unitFrames.frames[key]
			return config and config.castbarMode == "DETACHED"
		end
		self.SlickCastbarHolder = holder
		SlickUI.UnitFrames[moverKey] = holder
		SlickUI.Movers:Register(holder, moverKey, CastbarLabels[unit])
	end
end

local function ApplyCastbarLayout(frame)
	local castbar = frame and frame.Castbar
	local unit = frame and frame.SlickConfigKey
	local settings = unit and SlickUI.DB.unitFrames.frames[unit]
	if not castbar or not settings then return end
	if InCombatLockdown() then
		frame.SlickResourceLayoutPending = true
		return
	end

	local mode = settings.castbarMode or "ATTACHED"
	local width = math.max(80, tonumber(settings.castbarWidth) or settings.width)
	local height = math.max(8, tonumber(settings.castbarHeight) or 18)
	if mode == "IN_FRAME" then
		width = math.max(1, frame.Health:GetWidth())
		height = math.max(1, frame.Health:GetHeight())
	end
	castbar:SetSize(width, height)
	LayoutCastbarContents(castbar, height, mode == "IN_FRAME")
	local holder = frame.SlickCastbarHolder
	if holder then holder:SetSize(width, height) end

	castbar:ClearAllPoints()
	if mode == "DETACHED" and holder then
		castbar:SetPoint("CENTER", holder, "CENTER", 0, 0)
	elseif mode == "IN_FRAME" then
		castbar:SetAllPoints(frame.Health)
	else
		local summons = unit == "player" and frame.SlickTotems
		local stagger = unit == "player" and frame.Stagger
		local resource = unit == "player" and frame.SlickActiveResource
		if summons then
			castbar:SetPoint("TOP", summons, "BOTTOM", 0, -5)
		elseif stagger and stagger:IsShown() then
			castbar:SetPoint("TOP", stagger, "BOTTOM", 0, -5)
		elseif resource and resource:IsShown() then
			castbar:SetPoint("TOP", resource, "BOTTOM", 0, -5)
		elseif unit == "player" and frame.SlickPlayerInformationEffects then
			castbar:SetPoint("TOP", frame.SlickPlayerInformationEffects, "BOTTOM", 0, -5)
		else
			castbar:SetPoint("TOP", frame, "BOTTOM", 0, -5)
		end
	end
	SetCastbarInformationSwap(castbar, mode == "IN_FRAME" and (castbar.casting or castbar.channeling or castbar.empowering))

	if holder and holder.SlickMoverOverlay then
		local moverVisible = not SlickUI.DB.locked
			and mode == "DETACHED"
			and SlickUI.Movers:IsMoverOverlayVisible(holder.SlickMoverKey)
		holder.SlickMoverOverlay:EnableMouse(moverVisible)
		holder.SlickMoverOverlay:SetShown(moverVisible)
		holder:EnableMouse(moverVisible)
	end
end

local function AnchorPlayerCastbar(frame)
	ApplyCastbarLayout(frame)
end

local function AnchorPlayerSecondaryResources(frame)
	if not frame then return end
	if InCombatLockdown() then
		frame.SlickResourceLayoutPending = true
		return
	end
	frame.SlickResourceLayoutPending = nil
	local resource = frame.SlickActiveResource
	local stagger = frame.Stagger
	local summons = frame.SlickTotems
	local infoStrip = frame.SlickPlayerInformationEffects
	for _, element in ipairs({ frame.SlickClassPower, frame.SlickRunes, frame.SlickVigor }) do
		if element then
			element:ClearAllPoints()
			if infoStrip then
				element:SetPoint("TOPLEFT", infoStrip, "BOTTOMLEFT", -2, -3)
			else
				element:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, -3)
			end
		end
	end
	if stagger then
		stagger:ClearAllPoints()
		if resource and resource:IsShown() then
			stagger:SetPoint("TOPLEFT", resource, "BOTTOMLEFT", 0, -3)
		else
			stagger:SetPoint("TOPLEFT", infoStrip or frame, "BOTTOMLEFT", infoStrip and -2 or 0, -3)
		end
	end
	if summons then
		summons:ClearAllPoints()
		if stagger and stagger:IsShown() then
			summons:SetPoint("TOPLEFT", stagger, "BOTTOMLEFT", 0, -3)
		elseif resource and resource:IsShown() then
			summons:SetPoint("TOPLEFT", resource, "BOTTOMLEFT", 0, -3)
		else
			summons:SetPoint("TOPLEFT", infoStrip or frame, "BOTTOMLEFT", infoStrip and -2 or 0, -3)
		end
	end
	AnchorPlayerCastbar(frame)
end

local function CreatePlayerTotems(frame)
	local count, size, spacing = 4, 25, 3
	local row = CreateFrame("Frame", nil, frame)
	row:SetSize((count * size) + ((count - 1) * spacing), size)
	row:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, -3)
	local totems = {}
	for index = 1, count do
		local button = CreateFrame("Button", nil, row, "BackdropTemplate,SecureActionButtonTemplate")
		button:SetSize(size, size)
		button:SetPoint("LEFT", row, "LEFT", (index - 1) * (size + spacing), 0)
		button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
		button:SetAttribute("type", "destroytotem")
		button:SetAttribute("totem-slot", index)
		button:SetAttribute("useOnKeyDown", false)
		button:SetAlpha(0)
		Theme:CreatePanel(button)
		local icon = button:CreateTexture(nil, "ARTWORK")
		icon:SetPoint("TOPLEFT", 2, -2)
		icon:SetPoint("BOTTOMRIGHT", -2, 2)
		icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
		button.Icon = icon
		local cooldown = CreateFrame("Cooldown", nil, button, "CooldownFrameTemplate")
		cooldown:SetAllPoints(icon)
		cooldown:SetDrawEdge(false)
		cooldown:SetHideCountdownNumbers(false)
		button.Cooldown = cooldown
		totems[index] = button
	end
	frame.Totems = totems
	frame.SlickTotems = row
end

local function StyleAuraButton(_, button)
	button.Icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
	button.Icon:SetDrawLayer("ARTWORK")
	button.Cooldown:SetDrawEdge(false)
	button.Cooldown:SetHideCountdownNumbers(false)
	Theme:CreatePanel(button)
	Theme:SetFont(button.Count, 10)
	button.Count:ClearAllPoints()
	button.Count:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -1, 1)
	local mine = button:CreateTexture(nil, "OVERLAY")
	mine:SetColorTexture(unpack(Theme.Colors.border))
	mine:SetPoint("TOPLEFT", button, "TOPLEFT", 2, -2)
	mine:SetSize(5, 5)
	mine:Hide()
	button.SlickMine = mine
end

local function UpdateAuraButton(_, button, _, data)
	button.SlickMine:SetShown(button.isHarmfulAura == true and data.isPlayerAura == true)
end

local function IsReadable(value)
	if canaccessvalue and not canaccessvalue(value) then return false end
	if issecretvalue and issecretvalue(value) then return false end
	return value ~= nil
end

local function IsKnownActiveSpell(spellID)
	if not IsReadable(spellID) or type(IsPlayerSpell) ~= "function" then
		return false
	end

	local ok, known = pcall(IsPlayerSpell, spellID)
	if not ok or not IsReadable(known) or known ~= true then
		return false
	end

	local passiveFunction = C_Spell and C_Spell.IsSpellPassive or IsPassiveSpell
	if type(passiveFunction) == "function" then
		local passiveOK, passive = pcall(passiveFunction, spellID)
		if passiveOK and IsReadable(passive) and passive == true then
			return false
		end
	end

	return true
end

local ExactConsumableBuffNames = {
	["flask of the shattered sun"] = true,
	["flask of the blood knights"] = true,
	["flask of the magisters"] = true,
	["flask of thalassian resistance"] = true,
	["thalassian phoenix oil"] = true,
	["void-touched augment rune"] = true,
}

local function IsRecognizedConsumableBuff(name)
	if not IsReadable(name) or type(name) ~= "string" then
		return false
	end

	local normalized = string.lower(name)
	return ExactConsumableBuffNames[normalized] == true
		or string.find(normalized, "well fed", 1, true) ~= nil
		or string.find(normalized, "flask of ", 1, true) == 1
		or string.find(normalized, "phial of ", 1, true) == 1
		or string.find(normalized, " augment rune", 1, true) ~= nil
		or string.find(normalized, " oil", 1, true) ~= nil
end

local function CreateAuraRow(self, auraType)
	local row = CreateFrame("Frame", nil, self)
	row.__owner = self
	row.initialAnchor = "BOTTOMLEFT"
	row.growthX = "RIGHT"
	row.growthY = "UP"
	row.reanchorIfVisibleChanged = true
	row.showStealableBuffs = auraType == "buffs"
	row.showDebuffType = auraType == "debuffs"
	row.PostCreateButton = StyleAuraButton
	row.PostUpdateButton = UpdateAuraButton
	if auraType == "buffs" then
		self.Buffs = row
	else
		self.Debuffs = row
	end
	return row
end

local function InitializeManagedAuraButton(iconSize)
	return function(button)
		button:SetSize(iconSize, iconSize)

		local icon = button:CreateTexture(nil, "ARTWORK")
		icon:SetAllPoints(button)
		icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

		local cooldown = CreateFrame("Cooldown", nil, button, "CooldownFrameTemplate")
		cooldown:SetAllPoints(button)
		cooldown:SetDrawEdge(false)
		cooldown:SetHideCountdownNumbers(false)

		local count = button:CreateFontString(nil, "OVERLAY")
		Theme:SetFont(count, 10)
		count:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -1, 1)

		-- Aura buttons become restricted after this initializer returns. Register
		-- every display region while Blizzard still permits setup, then let the
		-- native container update them without SlickUI reading secret aura data.
		button:SetIcon(icon)
		button:SetDurationCooldown(cooldown)
		button:SetApplicationCount(count, {})
		if button.SetMouseClickEnabled then
			pcall(button.SetMouseClickEnabled, button, false)
		end
	end
end

local function CreateManagedAuraRow(frame, auraType, settings)
	local container = CreateFrame("AuraContainer", nil, frame, "CustomAuraContainerTemplate")
	container:SetSize(1, 1)
	container.SlickAuraType = auraType
	container.SlickSettings = settings

	local iconSize = settings.iconSize
	local spacing = settings.spacing
	container:AddAuraGroup("SlickUI_" .. auraType, auraType == "buffs" and "HELPFUL" or "HARMFUL", {
		maxFrameCount = settings.maxIcons,
		initializeFrame = InitializeManagedAuraButton(iconSize),
		layout = {
			elementWidth = iconSize,
			elementHeight = iconSize,
			elementSpacing = spacing,
			lineSpacing = spacing,
		},
	})
	-- Unit assignment must come after the group is declared or the native
	-- container will not register its UNIT_AURA feed.
	container:SetUnit(frame.SlickUnit)
	container:UpdateAllAuras()

	if auraType == "buffs" then
		frame.SlickManagedBuffs = container
	else
		frame.SlickManagedDebuffs = container
	end
	return container
end

local function FilterKnownSpellAndConsumableBuffs(element, unit, data)
	-- Requiring a duration removes permanent account, equipment, profession, and
	-- world effects before the spellbook/consumable tests are considered.
	if C_UnitAuras.GetAuraDuration(unit, data.auraInstanceID) == nil then
		return false
	end

	if IsKnownActiveSpell(data.spellId) then
		return true
	end

	return element.includeRecognizedConsumables == true
		and IsRecognizedConsumableBuff(data.name)
end

local function GetHorizontalAuraLayout(frameWidth, settings)
	local iconSize = settings.iconSize
	local spacing = settings.spacing
	local count = settings.maxIcons
	local columns = math.max(1, math.min(count, math.floor((frameWidth + spacing) / (iconSize + spacing))))
	local rows = math.ceil(count / columns)
	local height = rows * iconSize + math.max(0, rows - 1) * spacing
	return columns, height
end

local function SetManagedAuraMaximumLineSize(container, size)
	if not container then return end
	local setter = container.SetFlowLayoutMaximumLineSize or container.SetAuraLayoutRowWidth
	if setter then
		setter(container, size)
	end
end

local function ConfigureAuraRow(frame, auraType, settings, sibling)
	local row
	if UsesManagedAuraContainers then
		row = auraType == "buffs" and frame.SlickManagedBuffs or frame.SlickManagedDebuffs
	else
		row = auraType == "buffs" and frame.Buffs or frame.Debuffs
	end
	if not row then return end
	local iconSize = settings.iconSize
	local spacing = settings.spacing
	local count = settings.maxIcons
	local sameSideOffset = 0
	local frameWidth = frame:GetWidth()
	local horizontalColumns, horizontalHeight = GetHorizontalAuraLayout(frameWidth, settings)
	if auraType == "debuffs" and sibling.enabled and sibling.position == settings.position then
		if settings.position == "ABOVE" or settings.position == "BELOW" then
			local _, siblingHeight = GetHorizontalAuraLayout(frameWidth, sibling)
			sameSideOffset = siblingHeight + 2
		else
			sameSideOffset = sibling.iconSize + sibling.spacing + 2
		end
	end

	if not UsesManagedAuraContainers then
		row.size = iconSize
		row.spacing = spacing
		row.num = count
		row.onlyShowPlayer = false
		row.includeRecognizedConsumables = settings.includeConsumables == true
		row.FilterAura = auraType == "buffs" and settings.knownSpellsOnly == true and FilterKnownSpellAndConsumableBuffs or nil
		row.maxCols = count
		row.anchoredButtons = 0
	end
	row:ClearAllPoints()
	if settings.position == "BELOW" then
		row.initialAnchor = "TOPLEFT"
		row.growthX = "RIGHT"
		row.growthY = "DOWN"
		row:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, -4 - sameSideOffset)
		if not UsesManagedAuraContainers then row.maxCols = horizontalColumns end
		if UsesManagedAuraContainers then SetManagedAuraMaximumLineSize(row, frameWidth) end
		row:SetSize(frameWidth, horizontalHeight)
	elseif settings.position == "LEFT" then
		row.initialAnchor = "BOTTOMRIGHT"
		row.growthX = "LEFT"
		row.growthY = "UP"
		if not UsesManagedAuraContainers then row.maxCols = 1 end
		row:SetPoint("BOTTOMRIGHT", frame, "BOTTOMLEFT", -4 - sameSideOffset, 0)
		row:SetSize(iconSize, count * iconSize + math.max(0, count - 1) * spacing)
	elseif settings.position == "RIGHT" then
		row.initialAnchor = "BOTTOMLEFT"
		row.growthX = "RIGHT"
		row.growthY = "UP"
		if not UsesManagedAuraContainers then row.maxCols = 1 end
		row:SetPoint("BOTTOMLEFT", frame, "BOTTOMRIGHT", 4 + sameSideOffset, 0)
		row:SetSize(iconSize, count * iconSize + math.max(0, count - 1) * spacing)
	else
		row.initialAnchor = "BOTTOMLEFT"
		row.growthX = "RIGHT"
		row.growthY = "UP"
		row:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 0, 4 + sameSideOffset)
		if not UsesManagedAuraContainers then row.maxCols = horizontalColumns end
		if UsesManagedAuraContainers then SetManagedAuraMaximumLineSize(row, frameWidth) end
		row:SetSize(frameWidth, horizontalHeight)
	end
end

local function LayoutResourceSegments(element, count, width)
	count = math.max(1, count or #element)
	local spacing = 2
	local segmentWidth = math.max(1, (width - spacing * (count - 1)) / count)
	for index, bar in ipairs(element) do
		bar:ClearAllPoints()
		bar:SetWidth(segmentWidth)
		if index == 1 then
			bar:SetPoint("LEFT", element, "LEFT", 0, 0)
		else
			bar:SetPoint("LEFT", element[index - 1], "RIGHT", spacing, 0)
		end
	end
end

local function CreateSegmentedResource(frame, count)
	local element = CreateFrame("Frame", nil, frame)
	element:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, -3)
	element:SetSize(frame:GetWidth(), 7)
	for index = 1, count do
		local bar = CreateFrame("StatusBar", nil, element)
		bar:SetHeight(7)
		bar:SetStatusBarTexture(Theme.Texture)
		bar:SetMinMaxValues(0, 1)
		bar:SetValue(0)
		bar:Hide()
		Theme:CreateBarBackground(bar)
		element[index] = bar
	end
	LayoutResourceSegments(element, count, frame:GetWidth())
	return element
end

local function CreatePlayerClassResources(frame)
	local classPower = CreateSegmentedResource(frame, 10)
	classPower:Hide()
	classPower.PostUpdate = function(element, _, maximum, maximumChanged)
		if maximumChanged and maximum and maximum > 0 then
			LayoutResourceSegments(element, maximum, frame:GetWidth())
		end
	end
	classPower.PostVisibility = function(element, visible)
		element:SetShown(visible == true)
		if frame.SlickVigor and frame.SlickVigor.SlickVisible then
			frame.SlickActiveResource = frame.SlickVigor
		elseif visible == true then
			frame.SlickActiveResource = element
		elseif frame.SlickActiveResource == element then
			frame.SlickActiveResource = nil
		end
		AnchorPlayerSecondaryResources(frame)
	end
	frame.ClassPower = classPower

	local runes = CreateSegmentedResource(frame, 6)
	runes.colorSpec = true
	local _, classToken = UnitClass("player")
	if classToken == "DEATHKNIGHT" then
		runes:Show()
		frame.SlickActiveResource = runes
	else
		runes:Hide()
	end
	frame.Runes = runes

	frame.SlickClassPower = classPower
	frame.SlickRunes = runes

	-- Skyriding Vigor is exposed as alternate player power. Polling avoids the
	-- late RegisterEvent calls that current clients can reject for tainted UI.
	local vigor = CreateSegmentedResource(frame, 10)
	vigor:SetAlpha(0)
	vigor.SlickVisible = false
	for _, segment in ipairs(vigor) do
		segment:SetStatusBarColor(0.18, 0.72, 1.00, 1)
		segment:Show()
	end
	frame.SlickVigor = vigor

	local stagger = CreateFrame("StatusBar", nil, frame)
	stagger:SetSize(frame:GetWidth(), 9)
	stagger:SetStatusBarTexture(Theme.Texture)
	stagger:SetMinMaxValues(0, 1)
	stagger:SetValue(0)
	Theme:CreateBarBackground(stagger)
	local staggerText = Theme:CreateText(stagger, 9)
	staggerText:SetPoint("CENTER")
	staggerText:SetText("Stagger")
	stagger.Text = staggerText
	stagger.PostVisibility = function(_, visible)
		AnchorPlayerSecondaryResources(frame)
	end
	frame.Stagger = stagger
	frame.SlickStagger = stagger
	CreatePlayerTotems(frame)
	AnchorPlayerSecondaryResources(frame)
end

local function SafePowerNumber(value)
	if canaccessvalue and not canaccessvalue(value) then return nil end
	if issecretvalue and issecretvalue(value) then return nil end
	return type(value) == "number" and value or nil
end

local function UpdatePlayerVigor()
	local frame = SlickUI.UnitFrames and SlickUI.UnitFrames.player
	local vigor = frame and frame.SlickVigor
	if not vigor or type(UnitPowerBarID) ~= "function" then return end
	local barID = SafePowerNumber(UnitPowerBarID("player"))
	local info = barID and GetUnitPowerBarInfoByID(barID) or nil
	local current = info and SafePowerNumber(UnitPower("player", Enum.PowerType.Alternate)) or nil
	local maximum = info and SafePowerNumber(UnitPowerMax("player", Enum.PowerType.Alternate)) or nil
	local visible = current ~= nil and maximum ~= nil and maximum > 0 and maximum <= #vigor
	if visible then
		vigor.SlickMaximum = maximum
		LayoutResourceSegments(vigor, maximum, frame:GetWidth())
		for index, segment in ipairs(vigor) do
			segment:SetValue(math.max(0, math.min(1, current - index + 1)))
			segment:SetAlpha(index <= maximum and 1 or 0)
		end
	end
	if vigor.SlickVisible ~= visible then
		vigor.SlickVisible = visible
		vigor:SetAlpha(visible and 1 or 0)
		if visible then
			frame.SlickActiveResource = vigor
		elseif frame.SlickActiveResource == vigor then
			if frame.SlickClassPower and frame.SlickClassPower:IsShown() then
				frame.SlickActiveResource = frame.SlickClassPower
			elseif frame.SlickRunes and frame.SlickRunes:IsShown() then
				frame.SlickActiveResource = frame.SlickRunes
			else
				frame.SlickActiveResource = nil
			end
		end
		AnchorPlayerSecondaryResources(frame)
	end
end

C_Timer.NewTicker(0.20, UpdatePlayerVigor)

-- Resource visibility can change while mounting a combat vehicle. The player
-- frame is protected then, so replay only the cosmetic anchor work afterward.
local resourceLayoutEvents = CreateFrame("Frame")
resourceLayoutEvents:RegisterEvent("PLAYER_REGEN_ENABLED")
resourceLayoutEvents:SetScript("OnEvent", function()
	local frame = SlickUI.UnitFrames and SlickUI.UnitFrames.player
	if frame and frame.SlickResourceLayoutPending then
		AnchorPlayerSecondaryResources(frame)
	end
end)

local function Style(self, unit)
	local configKey = GetConfigKey(unit)
	local settings = GetSettings(unit)
	self.SlickUnit = unit
	self.SlickConfigKey = configKey
	self:SetSize(settings.width, settings.height)
	self:SetScale(settings.scale)
	self:RegisterForClicks("AnyUp")
	self:SetAttribute("*type1", "target")
	self:SetAttribute("*type2", "togglemenu")
	self.SlickMenuType = unit == "player" and "SELF" or (unit == "pet" and "PET" or "TARGET")
	self.menu = SlickUI.OpenUnitMenu
	self:SetFrameStrata("LOW")
	self:SetScript("OnEnter", UnitFrame_OnEnter)
	self:SetScript("OnLeave", UnitFrame_OnLeave)

	Theme:CreatePanel(self)

	local power = CreateFrame("StatusBar", nil, self)
	power:SetStatusBarTexture(Theme.Texture)
	power:SetStatusBarColor(unpack(Theme.Colors.power))
	power:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", 2, 2)
	power:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -2, 2)
	power:SetHeight(settings.powerHeight)
	Theme:CreateBarBackground(power)
	power.colorPower = true
	self.Power = power

	-- Hybrid resource specs such as Elemental and Enhancement Shaman use a
	-- non-mana primary resource while retaining mana for healing and utility.
	-- oUF's AdditionalPower element follows Blizzard's supported display-pair
	-- table, so this thin line appears only when mana is genuinely secondary.
	if unit == "player" then
		local additionalPower = CreateFrame("StatusBar", nil, power)
		additionalPower:SetStatusBarTexture(Theme.Texture)
		additionalPower:SetPoint("BOTTOMLEFT", power, "BOTTOMLEFT", 0, 0)
		additionalPower:SetPoint("BOTTOMRIGHT", power, "BOTTOMRIGHT", 0, 0)
		additionalPower:SetHeight(2)
		additionalPower:SetFrameLevel(power:GetFrameLevel() + 3)
		additionalPower:SetStatusBarColor(0.12, 0.45, 1)
		additionalPower.colorPower = true
		self.AdditionalPower = additionalPower
		self.SlickSecondaryMana = additionalPower
	end

	local health = CreateFrame("StatusBar", nil, self)
	health:SetStatusBarTexture(Theme.Texture)
	health:SetStatusBarColor(unpack(Theme.Colors.health))
	health:SetPoint("TOPLEFT", self, "TOPLEFT", 2, -2)
	health:SetPoint("TOPRIGHT", self, "TOPRIGHT", -2, -2)
	health:SetPoint("BOTTOM", power, "TOP", 0, 2)
	Theme:CreateBarBackground(health)
	health.colorClass = SlickUI.DB.unitFrames.classColors
	health.colorReaction = true
	health.colorHealth = not SlickUI.DB.unitFrames.classColors
	health.smoothing = Enum.StatusBarInterpolation.Immediate
	health.PostUpdateColor = UpdateUnitFrameHealthColor
	self.Health = health

	local name = Theme:CreateText(health, settings.width > 150 and 13 or 11)
	name:SetPoint("TOPLEFT", health, "TOPLEFT", 5, -4)
	name:SetPoint("RIGHT", health, "RIGHT", -48, 0)
	name:SetJustifyH("LEFT")
	self:Tag(name, "[name]")
	self.SlickNameText = name

	local healthPercent = Theme:CreateText(health, 11)
	healthPercent:SetPoint("TOPRIGHT", health, "TOPRIGHT", -5, -4)
	healthPercent:SetJustifyH("RIGHT")
	self:Tag(healthPercent, "[perhp<$%]")
	self.SlickHealthText = healthPercent

	local level = Theme:CreateText(health, 10)
	level:SetPoint("BOTTOMLEFT", health, "BOTTOMLEFT", 5, 3)
	level:SetTextColor(unpack(Theme.Colors.mutedText))
	self:Tag(level, "[smartlevel]")
	self.SlickLevelText = level

	local status = Theme:CreateText(health, 10)
	status:SetPoint("BOTTOMRIGHT", health, "BOTTOMRIGHT", -5, 3)
	status:SetTextColor(unpack(Theme.Colors.mutedText))
	self:Tag(status, "[status]")
	self.SlickStatusText = status

	name:Hide()
	healthPercent:Hide()
	level:Hide()
	status:Hide()
	CreateFrameDisplay(self, health, configKey)

	local raidTarget = health:CreateTexture(nil, "OVERLAY")
	raidTarget:SetSize(20, 20)
	raidTarget:SetPoint("CENTER", self, "TOP", 0, 2)
	self.RaidTargetIndicator = raidTarget

	if configKey == "target" then
		local faction = health:CreateTexture(nil, "OVERLAY", nil, 5)
		faction:SetSize(30, 30)
		faction:SetPoint("CENTER", health, "CENTER", 0, 0)
		faction:Hide()
		self.SlickOpposingFactionIndicator = faction
	end

	if unit ~= "player" and unit ~= "pet" and configKey ~= "target" then
		local classification = Theme:CreateText(health, 9)
		classification:SetPoint("RIGHT", health, "LEFT", -6, 0)
		classification:SetJustifyH("RIGHT")
		classification:SetTextColor(unpack(Theme.Colors.goldText))
		self:Tag(classification, "[slickui:classification]")
		self.SlickClassificationBadge = classification
	end

	if unit == "player" then
		local combat = self:CreateTexture(nil, "OVERLAY", nil, 7)
		combat:SetSize(24, 24)
		combat:SetPoint("CENTER", self, "TOPLEFT", 1, -1)
		self.CombatIndicator = combat

		local pvp = self:CreateTexture(nil, "OVERLAY")
		pvp:SetSize(24, 24)
		pvp:SetPoint("LEFT", self, "RIGHT", 5, 0)
		self.PvPIndicator = pvp

		CreatePlayerClassResources(self)
		CreatePlayerInformationEffects(self, health)
		AnchorPlayerSecondaryResources(self)
	end

	if unit == "player" or unit == "target" or unit == "focus" then
		CreateCastbar(self, settings.width, unit)
		if unit == "player" then AnchorPlayerCastbar(self) end
	end

	if UsesManagedAuraContainers then
		local auraSettings = SlickUI.DB.unitFrames.auras[configKey]
		CreateManagedAuraRow(self, "buffs", auraSettings.buffs)
		CreateManagedAuraRow(self, "debuffs", auraSettings.debuffs)
	else
		CreateAuraRow(self, "buffs")
		CreateAuraRow(self, "debuffs")
	end
end

oUF:RegisterStyle("SlickUI", Style)

local function RegisterFrame(unit, globalName)
	local frame = oUF:Spawn(unit, globalName)
	SlickUI.UnitFrames[unit] = frame
	SlickUI.Movers:Register(frame, unit, Labels[unit])
	return frame
end

function SlickUI:ArrangeBossFrames()
	local anchor = self.UnitFrames.boss
	local settings = self.DB.unitFrames.frames.boss
	if not anchor or not settings then return false end
	local frameStep = settings.height * settings.scale + settings.spacing
	anchor:SetSize(settings.width * settings.scale, settings.height * settings.scale * 5 + settings.spacing * 4)
	for index, frame in ipairs(self.BossFrames) do
		frame:ClearAllPoints()
		frame:SetPoint("TOPLEFT", anchor, "TOPLEFT", 0, -(index - 1) * frameStep)
	end
	return true
end

function SlickUI:ApplyUnitFrameSettings(unit)
	if not self:IsAvailableOutOfCombat("Unit-frame changes") then
		return false
	end

	local frames = unit == "boss" and self.BossFrames or { self.UnitFrames[unit] }
	local settings = self.DB.unitFrames.frames[unit]
	if not settings or #frames == 0 then
		return false
	end

	if settings.enabled then
		oUF:DisableBlizzard(unit)
	else
		oUF:EnableBlizzard(unit)
	end

	for _, frame in ipairs(frames) do
		if frame then
			frame:SetSize(settings.width, settings.height)
			frame:SetScale(settings.scale)
			UpdateFrameDisplayWidths(frame)
			frame.Power:SetHeight(math.min(settings.powerHeight, settings.height - 8))
			if frame.SlickClassPower then
				frame.SlickClassPower:SetWidth(settings.width)
				LayoutResourceSegments(frame.SlickClassPower, frame.SlickClassPower.__max or #frame.SlickClassPower, settings.width)
			end
			if frame.SlickVigor then
				frame.SlickVigor:SetWidth(settings.width)
				LayoutResourceSegments(frame.SlickVigor, frame.SlickVigor.SlickMaximum or #frame.SlickVigor, settings.width)
			end
			if frame.SlickStagger then frame.SlickStagger:SetWidth(settings.width) end
			if frame.SlickRunes then
				frame.SlickRunes:SetWidth(settings.width)
				LayoutResourceSegments(frame.SlickRunes, #frame.SlickRunes, settings.width)
			end
			frame.Health.colorClass = self.DB.unitFrames.classColors
			frame.Health.colorReaction = true
			frame.Health.colorHealth = not self.DB.unitFrames.classColors
			frame.Health:ForceUpdate()

			frame.SlickNameText:Hide()
			frame.SlickHealthText:Hide()
			frame.SlickLevelText:Hide()
			frame.SlickStatusText:Hide()

			if frame.Castbar then
				ApplyCastbarLayout(frame)
			end
			if settings.enabled then
				frame:Enable()
			else
				frame:Disable()
				frame:Hide()
			end
		end
	end

	self:ApplyAuraSettings(unit)
	if unit == "boss" then self:ArrangeBossFrames() end

	return true
end

function SlickUI:ApplyAuraSettings(unit)
	local frames = unit == "boss" and self.BossFrames or { self.UnitFrames[unit] }
	local settings = self.DB.unitFrames.auras[unit]
	if not settings or #frames == 0 then
		return false
	end

	for _, frame in ipairs(frames) do
		if frame then
			ConfigureAuraRow(frame, "buffs", settings.buffs, settings.debuffs)
			ConfigureAuraRow(frame, "debuffs", settings.debuffs, settings.buffs)
			if not UsesManagedAuraContainers then frame:EnableElement("Auras") end
			for auraType, auraSettings in pairs(settings) do
				local row
				if UsesManagedAuraContainers then
					row = auraType == "buffs" and frame.SlickManagedBuffs or frame.SlickManagedDebuffs
				else
					row = auraType == "buffs" and frame.Buffs or frame.Debuffs
				end
				if auraSettings.enabled then
					row:Show()
					if UsesManagedAuraContainers then
						row:UpdateAllAuras()
					else
						row.needFullUpdate = true
						row:ForceUpdate()
					end
				else
					row:Hide()
				end
			end
		end
	end
	return true
end

function SlickUI:ApplyFrameDisplaySettings(unit, changedSlot)
	local frames = unit == "boss" and self.BossFrames or { self.UnitFrames[unit] }
	if #frames == 0 then
		return false
	end

	local display = self.DB.unitFrames.frameDisplays[unit]
	local fontSizes = self.DB.unitFrames.frameDisplayFontSizes[unit]
	for _, frame in ipairs(frames) do
		if frame and frame.SlickDisplaySlots then
			for slotKey, text in pairs(frame.SlickDisplaySlots) do
				if not changedSlot or changedSlot == slotKey then
					Theme:SetFont(text, fontSizes[slotKey] or 9)
					frame:Untag(text)
					text:SetText("")
					local selected = display[slotKey] or "none"
					local choice = PlayerDisplayChoiceByKey[selected]
					if choice and choice.tag then
						text:Show()
						frame:Tag(text, choice.tag)
						if text.UpdateTag then text:UpdateTag() end
					else
						text:Hide()
					end
				end
			end
			UpdateFrameDisplayWidths(frame)
		end
	end
	return true
end

function SlickUI:ApplyAllUnitFrameSettings()
	if not self:IsAvailableOutOfCombat("Unit-frame changes") then
		return false
	end

	for unit in pairs(self.DB.unitFrames.frames) do
		self:ApplyUnitFrameSettings(unit)
	end
	return true
end

function SlickUI:ArrangePersonalFrames()
	if not self:IsAvailableOutOfCombat("Frame arrangement") then
		return false
	end

	local config = self.DB.unitFrames
	local frames = config.frames
	local mainDistance = (frames.player.width * frames.player.scale + frames.target.width * frames.target.scale) / 2 + config.personalSpacing
	local smallDistance = (frames.pet.width * frames.pet.scale + frames.targettarget.width * frames.targettarget.scale) / 2 + config.personalSpacing
	local mainY = -205
	local secondY = mainY - math.max(frames.player.height * frames.player.scale, frames.target.height * frames.target.scale) / 2 - config.rowSpacing

	self.DB.positions.player = { x = -mainDistance / 2, y = mainY }
	self.DB.positions.target = { x = mainDistance / 2, y = mainY }
	self.DB.positions.pet = { x = -smallDistance / 2, y = secondY }
	self.DB.positions.targettarget = { x = smallDistance / 2, y = secondY }
	self.DB.positions.focus = { x = 0, y = secondY - frames.focus.height * frames.focus.scale - config.rowSpacing }

	for unit, frame in pairs(self.UnitFrames) do
		local position = self.DB.positions[unit]
		if position then
			frame:ClearAllPoints()
			frame:SetPoint("CENTER", UIParent, "CENTER", position.x, position.y)
		end
	end

	self:Print("Personal unit frames arranged with even spacing.")
	return true
end

function SlickUI:SetCastbarsEnabled(enabled)
	if not self:IsAvailableOutOfCombat("Cast-bar changes") then
		return
	end

	self.DB.showCastbars = enabled and true or false
	for unit, frame in pairs(self.UnitFrames) do
		if frame.Castbar then
			local settings = self.DB.unitFrames.frames[unit]
			local unitFrameEnabled = not settings or settings.enabled ~= false
			if self.DB.showCastbars and unitFrameEnabled then
				frame:EnableElement("Castbar")
				frame.Castbar:ForceUpdate()
			else
				-- Disabling oUF's player Castbar element also restores the native
				-- PlayerCastingBarFrame events that the element suppresses.
				frame:DisableElement("Castbar")
				frame.Castbar:Hide()
			end
		end
	end
end

local function SafeDiagnosticValue(value, fallback)
	if canaccessvalue and not canaccessvalue(value) then return fallback end
	if issecretvalue and issecretvalue(value) then return fallback end
	if value == nil or value == "" then return fallback end
	return tostring(value)
end

SLASH_SLICKUIBUFFS1 = "/slickbuffs"
SlashCmdList.SLICKUIBUFFS = function()
	if select(4, GetBuildInfo()) >= 120100 then
		SlickUI:Print("Buff diagnostics are unavailable on WoW 12.1 because aura enumeration is protected.")
		return
	end
	if InCombatLockdown() then
		SlickUI:Print("Buff diagnostics are unavailable during combat because aura slots may be protected.")
		return
	end
	local settings = SlickUI.DB.unitFrames.auras.player.buffs
	local diagnosticElement = { includeRecognizedConsumables = settings.includeConsumables == true }
	local slots = { C_UnitAuras.GetAuraSlots("player", "HELPFUL") }
	local shown = 0
	SlickUI:Print("Buffs currently accepted by the known-spell filter:")
	for index = 2, #slots do
		local data = C_UnitAuras.GetAuraDataBySlot("player", slots[index])
		if data and FilterKnownSpellAndConsumableBuffs(diagnosticElement, "player", data) then
			shown = shown + 1
			local name = SafeDiagnosticValue(data.name, "protected name")
			local spellID = SafeDiagnosticValue(data.spellId, "protected ID")
			local source = SafeDiagnosticValue(data.sourceUnit, "no source")
			SlickUI:Print(string.format("%d. %s — spell %s — source %s", shown, name, spellID, source))
		end
	end
	if shown == 0 then
		SlickUI:Print("None found.")
	end
end

oUF:Factory(function(self)
	self:SetActiveStyle("SlickUI")
	RegisterFrame("player", "SlickUI_Player")
	RegisterFrame("target", "SlickUI_Target")
	RegisterFrame("focus", "SlickUI_Focus")
	RegisterFrame("pet", "SlickUI_Pet")
	RegisterFrame("targettarget", "SlickUI_TargetTarget")

	local bossAnchor = CreateFrame("Frame", "SlickUI_BossDock", UIParent)
	bossAnchor.SlickMoverOnly = true
	SlickUI.UnitFrames.boss = bossAnchor
	SlickUI.Movers:Register(bossAnchor, "boss", Labels.boss)
	for index = 1, 5 do
		local frame = oUF:Spawn("boss" .. index, "SlickUI_Boss" .. index)
		SlickUI.BossFrames[index] = frame
	end
	SlickUI:ArrangeBossFrames()

	SlickUI:ApplyAllUnitFrameSettings()
	SlickUI:SetCastbarsEnabled(SlickUI.DB.showCastbars)
	SlickUI.Movers:SetLocked(SlickUI.DB.locked)
end)
