-- Portal Pro :: UI/Widgets/Chart.lua
-- Bar charts and column sparklines drawn from plain colour textures. No art assets,
-- no external library, and they scale cleanly.
--
-- Net income can be negative (a day of refunds), so bars are drawn from a baseline that
-- sits wherever zero falls — negative columns hang below it in the negative colour.

local ADDON_NAME, PP = ...
local UI, T, U = PP.UI, PP.Theme, PP.U

local function niceMax(value)
	if value <= 0 then return 1 end
	local magnitude = 10 ^ math.floor(math.log10(value))
	local normalized = value / magnitude
	local step
	if normalized <= 1 then step = 1
	elseif normalized <= 2 then step = 2
	elseif normalized <= 5 then step = 5
	else step = 10 end
	return step * magnitude
end

--- @param opts table { height, labelFormat = fun(item):string, valueFormat = fun(copper):string }
function UI:BarChart(parent, opts)
	opts = opts or {}
	local f = UI:Panel(parent, "raised")
	UI:Border(f, "borderSubtle")
	f.bars = {}
	f.data = {}

	local plot = CreateFrame("Frame", nil, f)
	plot:SetPoint("TOPLEFT", T.space.md, -T.space.md)
	plot:SetPoint("BOTTOMRIGHT", -T.space.md, T.space.lg)
	f.plot = plot

	local baseline = plot:CreateTexture(nil, "ARTWORK")
	baseline:SetHeight(1)
	baseline:SetPoint("LEFT")
	baseline:SetPoint("RIGHT")
	baseline:SetColorTexture(T:C("borderStrong"))
	f.baseline = baseline

	local maxLabel = UI:Text(f, "", T.fontSize.xs, "", "textMuted", "RIGHT")
	maxLabel:SetPoint("TOPRIGHT", -T.space.sm, -T.space.xs)
	f.maxLabel = maxLabel

	function f:SetData(data)
		self.data = data or {}
		self:Render()
	end

	function f:Render()
		local data = self.data
		local count = #data
		if count == 0 then return end

		local peak, trough = 0, 0
		for _, item in ipairs(data) do
			peak = math.max(peak, item.value or 0)
			trough = math.min(trough, item.value or 0)
		end

		local upper = niceMax(peak)
		local lower = trough < 0 and -niceMax(-trough) or 0
		local span = upper - lower
		if span <= 0 then span = 1 end

		local plotHeight = plot:GetHeight()
		local plotWidth = plot:GetWidth()
		local zeroY = plotHeight * (-lower / span)

		self.baseline:ClearAllPoints()
		self.baseline:SetPoint("LEFT", plot, "BOTTOMLEFT", 0, zeroY)
		self.baseline:SetPoint("RIGHT", plot, "BOTTOMRIGHT", 0, zeroY)

		self.maxLabel:SetText(opts.valueFormat and opts.valueFormat(upper) or tostring(upper))

		local gap = 4
		local barWidth = math.max(2, (plotWidth - gap * (count - 1)) / count)

		for i = 1, count do
			local item = data[i]
			local bar = self.bars[i]
			if not bar then
				bar = CreateFrame("Frame", nil, plot)
				bar.fill = bar:CreateTexture(nil, "ARTWORK")
				bar.fill:SetAllPoints()
				bar.label = UI:Text(plot, "", T.fontSize.xs, "", "textMuted", "CENTER")
				bar:EnableMouse(true) -- frames don't take mouse input by default
				self.bars[i] = bar
			end

			local value = item.value or 0
			local height = math.max(1, math.abs(value) / span * plotHeight)
			local positive = value >= 0

			bar:ClearAllPoints()
			bar:SetWidth(barWidth)
			bar:SetHeight(height)
			bar:SetPoint(positive and "BOTTOMLEFT" or "TOPLEFT", plot, "BOTTOMLEFT",
				(i - 1) * (barWidth + gap), positive and zeroY or zeroY)
			bar.fill:SetColorTexture(T:C(positive and "accent" or "negative"))
			bar:Show()

			bar.label:ClearAllPoints()
			bar.label:SetPoint("TOP", plot, "BOTTOMLEFT", (i - 1) * (barWidth + gap) + barWidth / 2, -2)
			bar.label:SetText(opts.labelFormat and opts.labelFormat(item, i) or (item.label or ""))

			UI:Tooltip(bar, item.label or "", function()
				return { opts.valueFormat and opts.valueFormat(value) or tostring(value) }
			end)
		end

		for i = count + 1, #self.bars do
			self.bars[i]:Hide()
			self.bars[i].label:SetText("")
		end
	end

	f:SetScript("OnSizeChanged", function(self) self:Render() end)
	return f
end

--------------------------------------------------------------------------------

--- Horizontal proportion bars — one row per category. Used for the destination breakdown,
--- where a vertical chart would waste space on six items with long names.
function UI:BreakdownList(parent, opts)
	opts = opts or {}
	local f = CreateFrame("Frame", nil, parent)
	f.rows = {}

	function f:SetData(data)
		local peak = 0
		for _, item in ipairs(data) do peak = math.max(peak, math.abs(item.value or 0)) end
		if peak == 0 then peak = 1 end

		local y = 0
		for i, item in ipairs(data) do
			local row = self.rows[i]
			if not row then
				row = CreateFrame("Frame", nil, self)
				row:SetHeight(22)
				row.icon = row:CreateTexture(nil, "ARTWORK")
				row.icon:SetSize(14, 14)
				row.icon:SetPoint("LEFT")
				row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
				row.name = UI:Text(row, "", T.fontSize.sm, "", "textSecondary")
				row.name:SetPoint("LEFT", row.icon, "RIGHT", T.space.sm, 0)
				row.name:SetWidth(96)
				row.track = row:CreateTexture(nil, "BACKGROUND")
				row.track:SetHeight(6)
				row.track:SetPoint("LEFT", row.name, "RIGHT", T.space.sm, 0)
				row.track:SetPoint("RIGHT", row, "RIGHT", -70, 0)
				row.track:SetColorTexture(T:C("base"))
				row.fill = row:CreateTexture(nil, "ARTWORK")
				row.fill:SetHeight(6)
				row.fill:SetPoint("LEFT", row.track, "LEFT")
				row.value = UI:Text(row, "", T.fontSize.sm, "", "textPrimary", "RIGHT")
				row.value:SetPoint("RIGHT")
				-- Bounded width: right-anchored only, it would otherwise grow leftwards
				-- over the bar for any large figure.
				row.value:SetWidth(64)
				self.rows[i] = row
			end

			row:ClearAllPoints()
			row:SetPoint("TOPLEFT", self, "TOPLEFT", 0, y)
			row:SetPoint("TOPRIGHT", self, "TOPRIGHT", 0, y)

			row.icon:SetTexture(item.icon)
			row.icon:SetShown(item.icon ~= nil)
			row.name:SetText(item.label or "")
			row.name:SetPoint("LEFT", row.icon, "RIGHT", item.icon and T.space.sm or -14, 0)

			local pct = math.abs(item.value or 0) / peak
			row.fill:SetWidth(math.max(1, row.track:GetWidth() * pct))
			row.fill:SetColorTexture(T:C((item.value or 0) >= 0 and "accent" or "negative"))
			row.value:SetText(opts.valueFormat and opts.valueFormat(item.value) or tostring(item.value))

			row:Show()
			y = y - 24
		end

		for i = #data + 1, #self.rows do self.rows[i]:Hide() end
		self:SetHeight(math.max(1, -y))
	end

	return f
end
