
local _, Ns = ...
local L = Ns.L
local statPool = {}
local PDFHooked
local classId = select(2, UnitClass("player"))
local rClass, gClass, bClass = GetClassColor(classId)
local setClassColor = { r=rClass, g=gClass, b=bClass, a=0}
Ns.eventChecked = false
Ns.RunCheck = nil
Ns.dataColor = ""
Ns.TextSep = ":"

local OpPoints = {
    TOPLEFT="BOTTOMLEFT",
    TOPRIGHT="BOTTOMRIGHT",
    TOP="BOTTOM",
}

Ns.HUDAnchor = nil

----------------------------------
--		Local Accociations		--
----------------------------------
local function CreateStat()
	if #statPool > 0 then
		local stat = tremove(statPool)
		-- font is restored by InitStat.. all other properties are reapplied by InsertStat.
		if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then
			stat:SetToDefaults()
			stat:SetDrawLayer("OVERLAY")
			stat.ValueText:SetToDefaults()
			stat.ValueText:SetDrawLayer("OVERLAY")
		end
		return stat
	end
	local f = SinStatsFrame.HUD:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	Mixin(f, Ns.StatMixin)
	f:SetVertexColor(1, 1, 1)
	f.ProfileSettings = {}

	f.BarBg = SinStatsFrame.HUD:CreateTexture(nil, "ARTWORK", nil, 6)
	f.BarBg:SetTexture("Interface/BUTTONS/WHITE8X8")
	f.BarBg:SetVertexColor(0.1, 0.1, 0.1, 0.6)
	f.BarBg:Hide()

	f.Bar = SinStatsFrame.HUD:CreateTexture(nil, "ARTWORK", nil, 7)
	f.Bar:SetTexture("Interface/BUTTONS/WHITE8X8")
	f.Bar:SetVertexColor(0.2, 0.6, 0.2, 1)
	f.Bar:Hide()

	f.ValueText = SinStatsFrame.HUD:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	f.ValueText:SetVertexColor(1, 1, 1)
	f.ValueText:SetJustifyV("TOP")
	f.ValueText:SetJustifyH("LEFT")
	f.ValueText:SetWordWrap(false)
	f.ValueText:Hide()
	return f
end

local function InsertStat(self, profile, statdata, statoptions)
	local newWidget = CreateStat()
	local newStat = { widget=newWidget, data=statdata, options=statoptions, func=Ns.FunctionList[statdata.stat], newcolumn=Ns.Profile.NewColumns and Ns.Profile.NewColumns[statdata.stat]}
	tinsert(self.StatList, newStat)
	newWidget:SetJustifyV("TOP")
	if profile.StatAlignment == "TOPLEFT" then
		newWidget:SetJustifyH("LEFT")
	elseif profile.StatAlignment == "TOP" then
		newWidget:SetJustifyH("CENTER")
	else
		newWidget:SetJustifyH("RIGHT")
	end
	newWidget:SetSize(0, 0)

	if statdata.events then
		for event, evendata in pairs(statdata.events) do
			if not Ns.EventList[event] then
				Ns.EventList[event] = {}
			end
			newStat.units = evendata
			tinsert(Ns.EventList[event], newStat)
		end
	end
	if statdata.onupdate then
		if statdata.updatespeed then
			Ns:RegisterForOtherUpdate(statdata.updatespeed, newStat)
		else
			tinsert(Ns.OnUpdateList, newStat)
		end
	end
	Ns.dataColor = ""
	if statoptions.ColorCustom then
		if statoptions.ColorClass then
			newWidget:SetVertexColor(setClassColor.r, setClassColor.g, setClassColor.b)
		else
			newWidget:SetVertexColor(statoptions.ColorName.r, statoptions.ColorName.g, statoptions.ColorName.b)
		end
		if statoptions.ColorValueEndable then
			Ns.dataColor = CreateColor(statoptions.ColorValue.r, statoptions.ColorValue.g, statoptions.ColorValue.b)
			Ns.dataColor = Ns.dataColor:GenerateHexColorMarkup()
		end
	else
		if profile.StatTextColor then
			if profile.ClassColors then
				newWidget:SetVertexColor(setClassColor.r, setClassColor.g, setClassColor.b)
			else
				newWidget:SetVertexColor(profile.StatTextColor.r, profile.StatTextColor.g, profile.StatTextColor.b)
			end
		end
		if profile.DataColor and profile.SplitColors then
			Ns.dataColor = CreateColor(profile.DataColor.r, profile.DataColor.g, profile.DataColor.b)
			Ns.dataColor = Ns.dataColor:GenerateHexColorMarkup()
		end
	end
	newWidget.DataColor = Ns.dataColor

	if newWidget.ValueText then
		-- Always re-apply these — CreateStat only sets them for new widgets, not pool-reused ones.
		newWidget.ValueText:SetJustifyV("TOP")
		newWidget.ValueText:SetJustifyH("LEFT")
		newWidget.ValueText:SetWordWrap(false)
		newWidget.ValueText:SetVertexColor(newWidget:GetTextColor())
	end
	if profile.TextSeparator then
		Ns.TextSep = profile.TextSeparator
	end
end

local function PDFOnShow(self)
	if Ns.Profile.PanelDisplay then
		if not Ns.Profile.LockHUD then
			SinStatsFrame:Show()
		end
		SinStatsFrame.HUD:Show()
	end
end

local function PDFOnHide(self)
	if Ns.Profile.PanelDisplay then
		SinStatsFrame:Hide()
		SinStatsFrame.HUD:Hide()
	end
end

local function KillStat(stat)
	tinsert(statPool, stat.widget)
	wipe(stat.widget.ProfileSettings)
	stat.widget:Hide()
	stat.widget:ClearAllPoints()
	stat.widget.BarShown = false
	stat.widget.BarCacheRatio = nil
	stat.widget.BarCacheWidth = nil
	stat.widget.AlignedBarWidth = nil
	stat.widget._cachedBarWidth = nil
	stat.widget._staticOnly = false
	if stat.widget.BarBg then
		stat.widget.BarBg:Hide()
		stat.widget.BarBg:ClearAllPoints()
	end
	if stat.widget.Bar then
		stat.widget.Bar:Hide()
		stat.widget.Bar:ClearAllPoints()
	end
	if stat.widget.ValueText then
		stat.widget.ValueText:Hide()
		stat.widget.ValueText:ClearAllPoints()
		stat.widget.ValueText:SetText("")
	end
end

------------------------------------------
--		Shared Functions and Data		--
------------------------------------------
-- Functions for controlling stat. display information (each stat. widget is assigned (Mixin() these functions)

Ns.StatMixin = {}

function Ns.StatMixin.UpdateText(self, data, funcdata)
    if funcdata == nil then return end  -- stat returned no value; leave display unchanged
    local statName
    local rawName = Ns.Profile.Stats[data.stat].CustName
    local hideStatName = rawName and rawName ~= "" and string.trim(rawName) == ""
    local custName = rawName and string.trim(rawName) or nil
    if hideStatName then
        statName = ""
    elseif custName and custName ~= "" then
    	statName = custName
    elseif data.stat == "Primary" and Ns.primaryAutoName then
    	statName = Ns.primaryAutoName
    else
    	statName = L[data.stat]
    end
    if Ns.Profile.StatTextCaps then
        statName = strupper(statName)
    end
    if Ns.AlignActive and self.ValueText then -- 2nd fontstring..
        local labelText, valueText
        if Ns.Profile.HideText then
            if Ns.Profile.IconAlignment == "TOPLEFT" then
                labelText = self.ProfileSettings.Icon
                valueText = format("%s%s", self.DataColor, funcdata)
            else
                labelText = ""
                valueText = format("%s%s %s", self.DataColor, funcdata, self.ProfileSettings.Icon)
            end
        elseif Ns.Profile.IconAlignment == "TOPLEFT" then
            labelText = format("%s %s%s", self.ProfileSettings.Icon, statName, Ns.TextSep)
            valueText = format("%s%s", self.DataColor, funcdata)
        else
            labelText = format("%s%s", statName, Ns.TextSep)
            valueText = format("%s%s %s", self.DataColor, funcdata, self.ProfileSettings.Icon)
        end
        self:SetText(labelText)
        self.ValueText:SetText(valueText)
        self.ValueText:Show()
    else
        if self.ValueText then self.ValueText:Hide() end
        if Ns.Profile.HideText then
            if Ns.Profile.IconAlignment == "TOPLEFT" then
                self:SetText(format("%s %s%s", self.ProfileSettings.Icon, self.DataColor, funcdata))
            else
                self:SetText(format("%s%s %s", self.DataColor, funcdata, self.ProfileSettings.Icon))
            end
        elseif Ns.Profile.IconAlignment == "TOPLEFT" then
		self:SetText(format("%s %s%s%s %s", self.ProfileSettings.Icon, statName, Ns.TextSep, self.DataColor, funcdata))
        else
            self:SetText(format("%s%s%s %s %s", statName, Ns.TextSep, self.DataColor, funcdata, self.ProfileSettings.Icon))
        end
    end
    if self._staticOnly then
        self:UpdateBar(data, 0)
    end
end

function Ns.StatMixin.UpdateBar(self, data, value, maxValue)
	if not self.Bar then return end
	local options = Ns.Profile.Stats[data.stat]
	if not options or (not options.ShowBar and not options.StaticBar) then
		if self.BarShown then
			self.Bar:Hide()
			if self.BarBg then self.BarBg:Hide() end
			self.BarShown = false
			self.BarCacheRatio = nil
			self.BarCacheWidth = nil
		end
		return
	end
	local totalWidth = self.AlignedBarWidth or self._cachedBarWidth
	if not totalWidth or totalWidth <= 0 then
		local w = self:GetWidth()
		if not (issecretvalue and issecretvalue(w)) and w > 0 then
			totalWidth = w
			self._cachedBarWidth = w
		else
			local pd = self.PanelData or Ns.Profile
			totalWidth = pd.StatWidth or 150
		end
	end

	if not options.ShowBar then
		-- taking out the trash
		if self.BarShown and self.BarCacheRatio == -1 and self.BarCacheWidth == totalWidth then
			return
		end
		self.Bar:ClearAllPoints()
		self.Bar:SetPoint("TOPLEFT", self, "TOPLEFT")
		self.Bar:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT")
		self.Bar:SetWidth(totalWidth)
		self.Bar:Show()
		if self.BarBg then self.BarBg:Hide() end
		self.BarShown = true
		self.BarCacheRatio = -1
		self.BarCacheWidth = totalWidth
		return
	end

	local maxVal = maxValue or 100
	if not maxValue then
		local mode = options.BarMaxMode or 1
		if mode == 1 then
			maxVal = (options.Cap_Percent and options.Cap_Percent > 0) and options.Cap_Percent or 100
		elseif mode == 2 then
			maxVal = (options.BarMaxCustom and options.BarMaxCustom > 0) and options.BarMaxCustom or 100
		end
	end
	-- convert to 0 for bar ratio only (secret values)
	local safeValue = (issecretvalue and issecretvalue(value)) and 0 or (value or 0)
	local ratio = (maxVal > 0) and math.min(safeValue / maxVal, 1) or 0

	if self.BarShown and self.BarCacheRatio == ratio and self.BarCacheWidth == totalWidth then
		return
	end
	self.BarCacheRatio = ratio
	self.BarCacheWidth = totalWidth
	self.BarShown = true

	if options.BarAutoColor then
		local r, g
		local colorRatio = (data.stat == "Threat") and (1 - ratio) or ratio
		if colorRatio > 0.5 then
			r = (1 - colorRatio) * 2
			g = 1
		else
			r = 1
			g = colorRatio * 2
		end
		self.Bar:SetVertexColor(r, g, 0, options.BarAlpha or 1)
	end

	local iconOffset = 0
	if self.ProfileSettings.Icon and self.ProfileSettings.Icon ~= "" and Ns.Profile.IconAlignment == "TOPLEFT" and Ns.Profile.StatIcons then
		local _, fontSize = self:GetFont()
		iconOffset = (fontSize or 0) + 2
	end

	if self.BarBg then
		self.BarBg:ClearAllPoints()
		self.BarBg:SetPoint("TOPLEFT", self, "TOPLEFT")
		self.BarBg:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT")
		self.BarBg:SetWidth(totalWidth)
		self.BarBg:Show()
	end

	local barAreaWidth = totalWidth - iconOffset
	local barWidth = ratio > 0 and (iconOffset + barAreaWidth * ratio) or 0
	self.Bar:ClearAllPoints()
	self.Bar:SetPoint("TOPLEFT", self, "TOPLEFT")
	self.Bar:SetPoint("BOTTOMRIGHT", self, "BOTTOMLEFT", barWidth, 0)
	self.Bar:Show()
end

Ns.OnUpdateList = {}
Ns.EventList = {}
Ns.StatToDefaultOrder = {}

local byPlayerClassId

local rawIconCache = setmetatable({}, {__mode = "k"})

function Ns:GetSpellIcon(lookup, flags)
	local icon
	if type(lookup) == "table" then
		if rawIconCache[lookup] ~= nil then
			icon = rawIconCache[lookup]
		elseif WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then
			if lookup.spell and not Ns.Band(flags, Ns.ForceIcon)  then
				icon = C_Spell.GetSpellInfo(lookup.spell)
				icon = icon.iconID
			elseif lookup.item and not Ns.Band(flags, Ns.ForceIcon)  then
				_,_,_,_,icon = C_Item.GetItemInfoInstant(lookup.item)
			elseif lookup.currency and not Ns.Band(flags, Ns.ForceIcon)  then
				icon = C_CurrencyInfo.GetCurrencyInfo(lookup.currency)
				if icon then icon = icon.iconFileID end
			elseif lookup.icon then
				icon = lookup.icon
				if not icon or strtrim(icon) == "" then
					icon = "NoIcon"
				end
				if not strfind(icon, "\\") and not strfind(icon, "/") then
					icon = Ns.TexturePath .. icon
				end
			end
			rawIconCache[lookup] = icon or false
		else
			if lookup.spell and not Ns.Band(flags, Ns.ForceIcon)  then
				icon = select(3, GetSpellInfo(lookup.spell))
			elseif lookup.item and not Ns.Band(flags, Ns.ForceIcon)  then
				icon = select(5, GetItemInfoInstant(lookup.item))
			elseif lookup.currency and not Ns.Band(flags, Ns.ForceIcon)  then
				icon = C_CurrencyInfo.GetCurrencyInfo(lookup.currency)
				if icon then icon = icon.iconFileID end
			elseif lookup.icon then
				icon = lookup.icon
				if not icon or strtrim(icon) == "" then
					icon = "NoIcon"
				end
				if not strfind(icon, "\\") and not strfind(icon, "/") then
					icon = Ns.TexturePath .. icon
				end
			end
			rawIconCache[lookup] = icon or false
		end

		if icon == false then icon = nil end
	else
		icon = lookup
	end
	if Ns.Band(flags, Ns.IgnoreFormat) then
		return icon
	end
	if icon then
		icon = "|T"..icon..":0|t"
	end
	return icon or ""
end

-- function Ns:GetStatColor(lookup, flags)
-- 	local statColors
-- 	if type(lookup) == "table" then
-- 		if lookup.options.Stat_Color then

-- 			--statColors = CreateColor(options.Stat_Color.r, options.Stat_Color.g, options.Stat_Color.b)
-- 			--print(lookup.options.Stat_Color.r)

-- 		else
-- 			return
-- 		end
-- 	else
-- 		statColors = lookup
-- 	end
-- 	if Ns.Band(flags, Ns.IgnoreColor) then
-- 		return statColors
-- 	end
-- 	if statColors then
-- 		statColors = statColors:GenerateHexColorMarkup()
-- 	end
-- 	return statColors or ""
-- end


function Ns:InitStat(self, profile, statdata)
	self.ProfileSettings.Icon = ""

	if profile.StatIcons and (statdata.spell or statdata.item or statdata.currency) then
    self.ProfileSettings.Icon = Ns:GetSpellIcon(statdata)
	end
	self:SetFont(Ns.LSM:Fetch("font", profile.StatFont), profile.StatFontSize, profile.StatFontFlags)
	if profile.FontShadow then
		self:SetShadowOffset(profile.ShadowX or 1, profile.ShadowY or -1)
		self:SetShadowColor(0, 0, 0, 1)
	else
		self:SetShadowOffset(0, 0)
	end
	if self.ValueText then
		self.ValueText:SetFont(Ns.LSM:Fetch("font", profile.StatFont), profile.StatFontSize, profile.StatFontFlags)
		if profile.FontShadow then
			self.ValueText:SetShadowOffset(profile.ShadowX or 1, profile.ShadowY or -1)
			self.ValueText:SetShadowColor(0, 0, 0, 1)
		else
			self.ValueText:SetShadowOffset(0, 0)
		end
	end
	--local h = self:GetHeight()
	self:SetText(" ")
	local curH = self:GetHeight()
	self:SetHeight((issecretvalue and issecretvalue(curH)) and (profile.StatFontSize or 12) or curH)
	-- Other options

	--self.ProfileSettings.fontColor = Ns:GetStatColor(statdata)
	--print(self.ProfileSettings.fontColor)

	local statOpts = profile.Stats and profile.Stats[statdata.stat]
	if self.Bar and statOpts and (statOpts.StaticBar or statOpts.ShowBar) then
		local barTexPath
		if statOpts.BarTexture then
			barTexPath = Ns.LSM:Fetch("statusbar", statOpts.BarTexture)
		end
		local barColor = statOpts.BarColor or { r=0.2, g=0.6, b=0.2 }
		local barAlpha = statOpts.BarAlpha or 1
		self.Bar:SetTexture(barTexPath or "Interface/BUTTONS/WHITE8X8")
		self.Bar:SetVertexColor(barColor.r, barColor.g, barColor.b, barAlpha)
		self.Bar:Hide()
		local bgColor = statOpts.BarBgColor or { r=0.1, g=0.1, b=0.1 }
		local bgAlpha = statOpts.BarBgAlpha or 0.6
		self.BarBg:SetTexture(barTexPath or "Interface/BUTTONS/WHITE8X8")
		self.BarBg:SetVertexColor(bgColor.r, bgColor.g, bgColor.b, bgAlpha)
		self.BarBg:Hide()
		self._staticOnly = statOpts.StaticBar and not statdata.options.ShowBar
	elseif self.Bar then
		self.Bar:Hide()
		self.BarBg:Hide()
		self._staticOnly = false
	end
end

function Ns:GetClass(class)
	if type(class) == "string" then
		return Ns.byPlayerClass[strupper(strtrim(class))]
	elseif type(class) == "number" then
		return byPlayerClassId[class]
	else
		print("Sinstats: No class listed for ", class)
	end
end

function Ns:FillDisplayOrder(orderedlist, profile, configfill)
	local added, removed
	if profile.CustomOrderList then
		for k, stat in ipairs(profile.CustomOrderList) do
			if profile.Stats[stat].Show then
				tinsert(orderedlist, Ns.DefaultOrder[Ns.StatToDefaultOrder[stat]])
			else
				removed = true
			end
		end
	end
	for k, v in ipairs(Ns.DefaultOrder) do
		if profile.Stats[v.stat].Show then
			local found
			if profile.CustomOrderList then
				for i, stat in ipairs(profile.CustomOrderList) do
					if stat == v.stat then
						found = true
						break
					end
				end
			end
			if not found then
				added = true
				if configfill then
					tinsert(orderedlist, { stat=v.stat, id=Ns.StatToDefaultOrder[v.stat], })
				else
					tinsert(orderedlist, v)
				end
			end
		end
	end
	return added, removed
end

function Ns:InitialiseVersionData()
	for k, v in ipairs(Ns.DefaultOrder) do
		Ns.StatToDefaultOrder[v.stat] = k
	end
end

function Ns:ResetHUD()
	Ns.Profile.HUDPos = Ns.Profile.HUDPos or {}
	wipe(Ns.Profile.HUDPos)
	Ns.Profile.HUDPos.point = "CENTER"
	Ns.Profile.HUDPos.y = 0
	Ns.Profile.HUDPos.x = 0
	Ns.Profile.HUDPos.scale = 1
end

-- made it so that no processing is done if background is set to 0. avoids unnecessary calculations
function Ns.ResizeBackground(profile, NumCols, NumRows, onerow)
	if not profile then
		profile = Ns.Profile
	end
	if not SinStatsFrame.HUD or not SinStatsFrame.HUD.StatList or #SinStatsFrame.HUD.StatList == 0 then return end
	local noBackground = (profile.HUDBgAlpha or 0) == 0 and not profile.HUDBorder
	-- NO early-return on noBackground here. SinStatsFrame:SetWidth() must always run
	-- LibWindow saves and restores the correct frame dimensions
	local width = 0
	local height = 0

	local function safeWidth(widget)
		local w = widget.AlignedBarWidth or widget:GetWidth()
		if issecretvalue and issecretvalue(w) then return profile.StatWidth or 150 end
		return w
	end
	local function safeHeight(widget)
		local h = widget:GetHeight()
		if issecretvalue and issecretvalue(h) then return profile.StatFontSize or 12 end
		return h
	end
	if onerow then
		local r = SinStatsFrame.HUD.StatList[#SinStatsFrame.HUD.StatList].widget:GetRight()
		local l = SinStatsFrame.HUD.StatList[1].widget:GetLeft()
		if not (issecretvalue and issecretvalue(r)) and not (issecretvalue and issecretvalue(l)) then
			width = r - l + 25
		else
			for _, v in ipairs(SinStatsFrame.HUD.StatList) do
				width = width + safeWidth(v.widget)
			end
			width = width + 25
		end
		height = NumRows * (safeHeight(SinStatsFrame.HUD.StatList[1].widget) + profile.StatSpacingV)
	elseif NumCols > 1 then
		local lastWidget = SinStatsFrame.HUD.StatList[#SinStatsFrame.HUD.StatList].widget
		local lastWidth = safeWidth(lastWidget)
		local lastLeft = lastWidget:GetLeft()
		local firstLeft = SinStatsFrame.HUD.StatList[1].widget:GetLeft()
		if not (issecretvalue and issecretvalue(lastLeft)) and not (issecretvalue and issecretvalue(firstLeft)) then
			width = (lastLeft - firstLeft) + lastWidth
		else
			width = NumCols * lastWidth + (NumCols - 1) * (profile.StatSpacingH or 5)
		end
	else
		for k, v in pairs(SinStatsFrame.HUD.StatList) do
			local w = safeWidth(v.widget)
			if w > width then
				width = w
			end
		end
		width = width + profile.StatSpacingH + 15
	end
	if not onerow then
		height = (NumRows * safeHeight(SinStatsFrame.HUD.StatList[1].widget)) + (NumRows - 1) * profile.StatSpacingV
	end
	height = height + 2
	if not noBackground then
		SinStatsFrame.HUD.Background:SetSize(width, height)
	end
	SinStatsFrame:SetWidth(width)
	Ns.UpdateMouseOverSize(width, height)
end


function Ns:AlignStatValues(profile, NumCols)
	if not Ns.AlignActive then
		if SinStatsFrame.HUD.StatList then
			for _, v in ipairs(SinStatsFrame.HUD.StatList) do
				if v.widget.ValueText then
					v.widget.ValueText:Hide()
					v.widget.ValueText:SetWidth(0)
				end
				v.widget.AlignedBarWidth = nil
			end
		end
		return
	end
	if not SinStatsFrame.HUD.StatList or #SinStatsFrame.HUD.StatList == 0 then return end

	-- ValueText:GetStringWidth() may return a secret value
	local maxLabelWidth = 0
	local maxValueWidth = 0
	local anyNonSecretLabel = false
	local anyNonSecretValue = false
	for _, v in ipairs(SinStatsFrame.HUD.StatList) do
		if v.widget then
			local lw = v.widget:GetStringWidth() or 0
			if not (issecretvalue and issecretvalue(lw)) then
				anyNonSecretLabel = true
				if lw > maxLabelWidth then maxLabelWidth = lw end
			end
			if v.widget.ValueText then
				local vw = v.widget.ValueText:GetStringWidth() or 0
				if issecretvalue and issecretvalue(vw) then
					-- secret skip. use cached width below
				else
					anyNonSecretValue = true
					if vw > maxValueWidth then maxValueWidth = vw end
				end
			end
		end
	end

	-- When all widths were secret, use the cache from the last non-secret pass.
	if anyNonSecretLabel then
		SinStatsFrame.HUD._cachedMaxLabelWidth = maxLabelWidth
	else
		maxLabelWidth = SinStatsFrame.HUD._cachedMaxLabelWidth or maxLabelWidth
	end
	if anyNonSecretValue then
		SinStatsFrame.HUD._cachedMaxValueWidth = maxValueWidth
	else
		maxValueWidth = SinStatsFrame.HUD._cachedMaxValueWidth or maxValueWidth
	end

	local gap = profile.AlignGap or 5
	local alignedWidth = maxLabelWidth + gap + maxValueWidth
	local statWidth = profile.StatWidth or 150
	local isMultiCol = NumCols and NumCols > 1

	local isRight = profile.StatAlignment == "TOPRIGHT"

	for _, v in ipairs(SinStatsFrame.HUD.StatList) do
		local vt = v.widget.ValueText
		if vt then
			vt:ClearAllPoints()
			if isRight then
				vt:SetPoint("TOPLEFT", v.widget, "TOPRIGHT", gap, 0)
				vt:SetJustifyH("RIGHT")
				if isMultiCol then
					local labelArea = math.max(1, statWidth - maxValueWidth - gap)
					vt:SetWidth(math.max(1, statWidth - labelArea - gap))
				else
					vt:SetWidth(maxValueWidth)
				end
			else
				vt:SetPoint("TOPLEFT", v.widget, "TOPLEFT", maxLabelWidth + gap, 0)
				vt:SetJustifyH("LEFT")
				if isMultiCol then
					local availWidth = statWidth - maxLabelWidth - gap
					vt:SetWidth(math.max(1, availWidth))
				else
					vt:SetWidth(0) -- auto-size for single column
				end
			end
			vt:Show()
		end

		if isMultiCol then
			if isRight then
				v.widget:SetWidth(math.max(1, statWidth - maxValueWidth - gap))
			end
			v.widget.AlignedBarWidth = statWidth
		elseif isRight then
			v.widget:SetWidth(maxLabelWidth)
			v.widget.AlignedBarWidth = alignedWidth
		else
			v.widget:SetWidth(alignedWidth)
			v.widget.AlignedBarWidth = alignedWidth
		end
		v.widget._cachedBarWidth = v.widget.AlignedBarWidth
	end

	if isRight then
		if isMultiCol then
			local widthDelta = maxValueWidth + gap
			for _, v in ipairs(SinStatsFrame.HUD.StatList) do
				if v.newcolumn then
					local p, r, rp, x, y = v.widget:GetPoint(1)
					v.widget:ClearAllPoints()
					v.widget:SetPoint(p, r, rp, (x or 0) + widthDelta, y or 0)
				end
			end
		elseif #SinStatsFrame.HUD.StatList > 0 then
			local w1 = SinStatsFrame.HUD.StatList[1].widget
			local p, r, rp, x, y = w1:GetPoint(1)
			w1:ClearAllPoints()
			w1:SetPoint(p, r, rp, (x or 0) - maxValueWidth - gap, y or 0)
		end
	end
end

----------------------------------
--	Profile Refresh (config)	--
----------------------------------
local profileDirty = false
function Ns:QueueProfileRefresh()
	if profileDirty then return end
	profileDirty = true
	C_Timer.After(0, function()
		profileDirty = false
		Ns:InitialiseProfile(Ns.Profile)
	end)
end

------------------------------------------
--		Initialise Stat. Widgets		--
------------------------------------------
function Ns:InitialiseProfile(profile)
	if not byPlayerClassId then
		byPlayerClassId = {}

		for k, v in pairs(Ns.byPlayerClass) do
			if not C_CreatureInfo.GetClassInfo(v) then
				Ns.byPlayerClass[k] = nil
			end
		end

		for k, v in pairs(Ns.byPlayerClass) do
			byPlayerClassId[v] = k
		end
	end
	if profile.HUDBgTexture then
		local texPath = Ns.LSM:Fetch("statusbar", profile.HUDBgTexture)
		SinStatsFrame.HUD.Background:SetTexture(texPath or "Interface/BUTTONS/WHITE8X8")
	end
	if profile.HUDBgColor then
		SinStatsFrame.HUD.Background:SetVertexColor(profile.HUDBgColor.r, profile.HUDBgColor.g, profile.HUDBgColor.b)
	end
	if profile.LockHUD or profile.HideHUD then
		SinStatsFrame:Hide()
	else
		SinStatsFrame:Show()
	end
	StaticPopupDialogs["SINSTATS_RESET"] = {
		text = L["ResetPosition"],
		button1 = "Yes",
		button2 = "No",
		OnAccept = function()
			profile.ResetPosition = false
			Ns:ResetHUD()
			Ns:InitialiseProfile(Ns.Profile)
		end,
		timeout = 0,
		whileDead = true,
		hideOnEscape = false,
		OnCancel = function()
			profile.ResetPosition = false
		end,
	}
	StaticPopupDialogs["SINSTATS_RESET_COUNTER"] = {
		text = L["Reset_CounterConfirm"] or "Reset counter?",
		button1 = "Yes",
		button2 = "No",
		OnAccept = function()
			Ns:QueueProfileRefresh()
		end,
		timeout = 0,
		whileDead = true,
		hideOnEscape = true,
		OnCancel = function() end,
	}
	if profile.ResetPosition then
		StaticPopup_Show("SINSTATS_RESET")
	end
	if not profile.HUDPos then
		Ns:ResetHUD()
	end

	Ns.LWin.RegisterConfig(SinStatsFrame, profile.HUDPos)
	Ns.LWin.RestorePosition(SinStatsFrame)
	if not Ns.PanelShowHooked then
		if profile.PanelShow then
			if WOW_PROJECT_ID == 14 then
				Ns.PanelShowHooked = true
				local function statsShow()
					if CharacterFrameExpandButton and PaperDollFrame:IsVisible() then
						if not CharacterStatsPane:IsShown() then
								CharacterFrameExpandButton:Click()
						end
					end
				end

				PaperDollFrame:HookScript("OnShow", statsShow)
			end
		else
			if WOW_PROJECT_ID == 14 then
				Ns.PanelShowHooked = true
				local function statsHide()
					if CharacterFrameExpandButton and PaperDollFrame:IsVisible() then
						if CharacterStatsPane:IsShown() then
								CharacterFrameExpandButton:Click()
						end
					end
				end
				PaperDollFrame:HookScript("OnShow", statsHide)
			end
		end
	end

	if profile.Stats.EventEnable and profile.Stats.EventWorld or profile.Stats.EventDungeon or profile.Stats.EventRaid or profile.Stats.EventPvP or profile.Stats.EventArena or profile.Stats.EventCombat or profile.Stats.EventDelves then
		Ns.eventChecked = true
	end

	if Ns.sshMiniButton:IsButtonCompartmentAvailable() then
		if profile.CompButton then
			Ns.sshMiniButton:AddButtonToCompartment("SinStats")
		else
			Ns.sshMiniButton:RemoveButtonFromCompartment("SinStats")
		end
	end

	if profile.Stats.EventEnable then
		local stats = profile.Stats
		local shouldHide = false
		local hasAnyEvent = stats.EventWorld or stats.EventDungeon or stats.EventRaid
			or stats.EventPvP or stats.EventArena or stats.EventCombat
			or stats.EventDelves or stats.EventResting or stats.EventMounted

		if hasAnyEvent then
			local inInstance, instanceType = IsInInstance()
			local INSTANCE_RULES = {
				{ type = "party",    key = "EventDungeon" },
				{ type = "raid",     key = "EventRaid" },
				{ type = "pvp",      key = "EventPvP" },
				{ type = "arena",    key = "EventArena" },
				{ type = "scenario", key = "EventDelves" },
			}

			if not profile.PanelDisplay and not stats.EventCombat then
				for _, rule in ipairs(INSTANCE_RULES) do
					local enabled = stats[rule.key]
					if enabled and instanceType == rule.type then
						-- Inside matching instance — show
					elseif enabled and not inInstance then
						shouldHide = true
					elseif not enabled and Ns.eventChecked and instanceType == rule.type then
						shouldHide = true
					end
				end

				if stats.EventWorld and instanceType == "none" then
					shouldHide = false
				end
			end

			if stats.EventCombat and not profile.PanelDisplay then
				shouldHide = not (UnitAffectingCombat("player") or InCombatLockdown())
			end

			if stats.EventResting and not profile.PanelDisplay and IsResting() then
				shouldHide = true
			end
			if stats.EventMounted and not profile.PanelDisplay and IsMounted() then
				shouldHide = true
			end
		end

		-- Apply result, respecting EventOpacity setting
		if shouldHide then
			local useOpacity = stats.EventOpacity
			local targetAlpha = useOpacity and (stats.EventOpacityValue or 0.3) or 0
			if not useOpacity then
				profile.HideHUD = true
			end
			SinStatsFrame.HUD:SetAlpha(targetAlpha)
			if targetAlpha <= 0 then
				SinStatsFrame.HUD:Hide()
			end
			if not profile.LockHUD then
				SinStatsFrame:SetAlpha(targetAlpha)
				if targetAlpha <= 0 then
					SinStatsFrame:Hide()
				end
			end
		else
			profile.HideHUD = false
			SinStatsFrame.HUD:SetAlpha(1)
			SinStatsFrame.HUD:Show()
			if not profile.LockHUD then
				SinStatsFrame:SetAlpha(1)
				SinStatsFrame:Show()
			end
		end
	end

	-- Background and border (after event eval so HideHUD is final)
	SinStatsFrame.HUD.Background:SetAlpha((profile.HideHUD and 0) or profile.HUDBgAlpha or 0)

	if SinStatsFrame.HUD.BorderLines then
		for i = 1, 4 do
			local line = SinStatsFrame.HUD.BorderLines[i]
			if profile.HUDBorder and not profile.HideHUD then
				local c = profile.HUDBorderColor or { r=0.1, g=0.1, b=0.1 }
				line:SetColorTexture(c.r, c.g, c.b, 1)
				line:SetThickness(profile.HUDBorderSize or 1)
				line:Show()
			else
				line:Hide()
			end
		end
	end

	if profile.HUDStrata == nil then profile.HUDStrata = "MEDIUM" end
	SinStatsFrame.HUD:SetFrameStrata(profile.HUDStrata)
	SinStatsFrame:SetFrameStrata(profile.HUDStrata)

	profile.Minimap.hide = not profile.Minimap.Show
	if profile.Minimap.Show then
		Ns.sshMiniButton:Show("SinStats")
	else
		Ns.sshMiniButton:Hide("SinStats")
	end
	SinStatsFrame.HUD:Hide()
	SinStatsFrame.HUD:UnregisterAllEvents() -- Stop event processing
	wipe(Ns.OnUpdateList)
	Ns:ClearOtherUpdates()
	wipe(Ns.EventList)
	for i=#SinStatsFrame.HUD.StatList, 1, -1 do
		KillStat(SinStatsFrame.HUD.StatList[i])
	end
	wipe(SinStatsFrame.HUD.StatList)

	if profile.HideHUD then
		return
	end

	local orderedList = {}
	local oneRow = profile.NewColumns and true or false
	Ns:FillDisplayOrder(orderedList, profile, nil)

	local isCompact = false
	if oneRow and not profile.RowLayoutRows and profile.CustomOrderList and #profile.CustomOrderList > 1 then
		isCompact = true
		for k = 2, #profile.CustomOrderList do
			if not profile.NewColumns[profile.CustomOrderList[k]] then
				isCompact = false
				break
			end
		end
	end

	local NumRows, NumCols, RowCount = 0, 1, 1
	for k, v in ipairs(orderedList) do
		if profile.Stats[v.stat] and profile.Stats[v.stat].Show then
			if isCompact and not profile.NewColumns[v.stat] then
				profile.NewColumns[v.stat] = true
			end
			if profile.NewColumns and #SinStatsFrame.HUD.StatList > 1 then
				if not profile.NewColumns[v.stat] then
					oneRow = false
				else
					NumCols = NumCols + 1
				end
			end
			InsertStat(SinStatsFrame.HUD, profile, v, profile.Stats[v.stat])
		end
	end
	if #SinStatsFrame.HUD.StatList == 0 then
		return
	end

	Ns.AlignActive = profile.AlignValues and not oneRow and (
		profile.StatAlignment == "TOPLEFT" or
		profile.StatAlignment == "TOPRIGHT"
	)
	local totalRows = #SinStatsFrame.HUD.StatList
	local firstOnRow, lastAnchor
	if totalRows > 0 then
		local rows = profile.StatRows or 1

		for k, v in ipairs(SinStatsFrame.HUD.StatList) do
			v.widget:ClearAllPoints()
			if not oneRow and NumCols > 1 then
				v.widget:SetWidth(profile.StatWidth or 100)
				v.widget._cachedBarWidth = profile.StatWidth or 100
			end
			if k == 1 then
				v.widget:SetPoint("TOPLEFT", SinStatsFrame, "BOTTOMLEFT")
				firstOnRow = v.widget
				lastAnchor = v.widget
			else
				if v.newcolumn then
					v.widget:SetPoint("LEFT", firstOnRow, "RIGHT", profile.StatSpacingH, 0)
					firstOnRow = v.widget
--					NumCols = NumCols + 1
					RowCount = 1
				else
					oneRow = false
					v.widget:SetPoint("TOPLEFT", lastAnchor, "BOTTOMLEFT", 0, -profile.StatSpacingV)
					RowCount = RowCount + 1
				end
			end
			lastAnchor = v.widget
			if RowCount > NumRows then
				NumRows = RowCount
			end
			v.widget:Show()
			Ns:InitStat(v.widget, profile, v.data)
		end
	end
	-- if NumCols == 1 and profile.StatAlignment ~= "TOPLEFT" then
        -- for i, v in ipairs(SinStatsFrame.HUD.StatList) do
            -- v.widget:ClearAllPoints()
            -- v.widget:SetPoint(profile.StatAlignment, i == 1 and SinStatsFrame or SinStatsFrame.HUD.StatList[i-1].widget, OpPoints[profile.StatAlignment], 0, i == 1 and 0 or -profile.StatSpacingV)

        -- end
    -- end
	if NumCols == 1 and profile.StatAlignment ~= "TOPLEFT" then
		for i, v in ipairs(SinStatsFrame.HUD.StatList) do
			v.widget:ClearAllPoints()
			v.widget:SetPoint(profile.StatAlignment, i == 1 and SinStatsFrame or SinStatsFrame.HUD.StatList[i-1].widget, OpPoints[profile.StatAlignment], 0, i == 1 and 0 or -profile.StatSpacingV)

		end
	elseif oneRow and profile.StatAlignment == "TOPRIGHT" then
		for i=#SinStatsFrame.HUD.StatList, 1, -1 do -- Anchor from last stat to first so we don't mess up the layout ordering!
			local v = SinStatsFrame.HUD.StatList[i]
			v.widget:ClearAllPoints()
			if i == #SinStatsFrame.HUD.StatList then
				v.widget:SetPoint("TOPRIGHT", SinStatsFrame, "BOTTOMRIGHT", 0, 0)
			else
				v.widget:SetPoint("RIGHT", SinStatsFrame.HUD.StatList[i + 1].widget, "LEFT", -profile.StatSpacingH, 0)
			end
		end
	end
	for event, widgets in pairs(Ns.EventList) do
		for widget, widgetinfo in pairs(widgets) do
			if type(widgetinfo.units) == "table" then
				local units = { player=true, target=true }
				for _,unit in pairs(widgetinfo.units) do
					units[unit] = true
					SinStatsFrame.HUD:GetScript("OnEvent")(SinStatsFrame.HUD, event, unit)
				end
				local unit1, unit2
				if units.player then
					unit1 = "player"
				end
				if units.target then
					if not unit1 then
						unit1 = "target"
					else
						unit2 = "target"
					end
				end
				SinStatsFrame.HUD:RegisterUnitEvent(event, unit1, unit2)
			else
				SinStatsFrame.HUD:RegisterEvent(event)
				SinStatsFrame.HUD:GetScript("OnEvent")(SinStatsFrame.HUD, event)
			end
		end
	end
	for event, eveninfo in pairs(Ns.GlobalEvents) do
		if type(eveninfo) == "table" then
			SinStatsFrame.HUD:RegisterUnitEvent(event, unpack(eveninfo))
			for _, unit in pairs(eveninfo) do
				SinStatsFrame.HUD:GetScript("OnEvent")(SinStatsFrame.HUD, event, unit)
			end
		else
			SinStatsFrame.HUD:RegisterEvent(event)
			SinStatsFrame.HUD:GetScript("OnEvent")(SinStatsFrame.HUD, event)
		end
	end

	if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then
		SinStatsFrame.HUD:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
	end
	SinStatsFrame.HUD.Elapsed = 0
	SinStatsFrame.HUD:GetScript("OnUpdate")(SinStatsFrame.HUD, 10)
	if NumCols == 1 then
		for i = 1, #SinStatsFrame.HUD.StatList do
			local v = SinStatsFrame.HUD.StatList[i]
			if v.func then v.func(v.widget, v.data, v.options) end
		end
	end
	Ns:AlignStatValues(profile, NumCols)

	if NumCols == 1 then
		for i = 1, #SinStatsFrame.HUD.StatList do
			local v = SinStatsFrame.HUD.StatList[i]
			if v.func then v.func(v.widget, v.data, v.options) end
		end
	end
	Ns.ResizeBackground(profile, NumCols, NumRows, oneRow)
	SinStatsFrame.HUD:Show()

	if profile.PanelDisplay then
		if not PDFHooked then
			PDFHooked = true
			PaperDollFrame:HookScript("OnShow", PDFOnShow)
			PaperDollFrame:HookScript("OnHide", PDFOnHide)
		end
		if not PaperDollFrame:IsVisible() then
			SinStatsFrame:Hide()
			SinStatsFrame.HUD:Hide()
		end
		Ns.CharPanel = true
		SinStatsFrame:Hide()
		SinStatsFrame.HUD:Hide()
	else
		Ns.CharPanel = false
	end

	if Ns.RefreshAllStatCounts then
		Ns:RefreshAllStatCounts()
	end

end