-- Portal Pro :: UI/Tab_Market.lua
-- What the competition is charging, harvested from adverts the lead scanner already reads.

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

local ROW_HEIGHT = 26
local OBS_HEIGHT = 26

local WINDOWS = {
	{ value = 3600,   label = "1 hour" },
	{ value = 21600,  label = "6 hours" },
	{ value = 86400,  label = "24 hours" },
	{ value = 604800, label = "7 days" },
}

local VERDICT = {
	under = { token = "positive", text = "undercutting" },
	at    = { token = "accent",   text = "at market" },
	over  = { token = "warning",  text = "above market" },
}

--------------------------------------------------------------------------------
-- Per-destination comparison rows
--------------------------------------------------------------------------------

local function createDestRow(parent)
	local row = CreateFrame("Frame", nil, parent)
	row:SetHeight(ROW_HEIGHT)

	row.icon = row:CreateTexture(nil, "ARTWORK")
	row.icon:SetSize(16, 16)
	row.icon:SetPoint("LEFT")
	row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

	row.name = UI:Text(row, "", T.fontSize.base, "", "textPrimary")
	row.name:SetPoint("LEFT", row.icon, "RIGHT", T.space.sm, 0)
	row.name:SetWidth(110)

	row.mine = UI:Text(row, "", T.fontSize.base, "", "textPrimary", "RIGHT")
	row.mine:SetPoint("LEFT", row.name, "RIGHT", T.space.sm, 0)
	row.mine:SetWidth(96)

	row.market = UI:Text(row, "", T.fontSize.base, "", "textSecondary", "RIGHT")
	row.market:SetPoint("LEFT", row.mine, "RIGHT", T.space.md, 0)
	row.market:SetWidth(96)

	row.verdict = UI:Text(row, "", T.fontSize.sm, "", "textMuted")
	row.verdict:SetPoint("LEFT", row.market, "RIGHT", T.space.md, 0)
	row.verdict:SetWidth(150)

	return row
end

local function updateDestRow(row, dest, window)
	row.icon:SetTexture(PP.Dest:Icon(dest))
	row.name:SetText(dest.name)
	row.mine:SetText(U.MoneyFull(PP.Dest:Price(dest)))

	local verdict, delta, stats = PP.Market:CompareToMarket(dest.key, window)

	if not verdict then
		row.market:SetText("-")
		row.market:SetTextColor(T:C("textMuted"))
		row.verdict:SetText("no data yet")
		row.verdict:SetTextColor(T:C("textMuted"))
		return
	end

	row.market:SetText(U.MoneyFull(stats.median))
	row.market:SetTextColor(T:C("textSecondary"))

	local style = VERDICT[verdict]
	local suffix = ""
	if verdict ~= "at" then
		suffix = (" by %s"):format(U.MoneyPlain(math.abs(delta)))
	end
	row.verdict:SetText(style.text .. suffix)
	row.verdict:SetTextColor(T:C(style.token))
end

--------------------------------------------------------------------------------
-- Observation rows
--------------------------------------------------------------------------------

local function createObsRow(parent)
	local row = UI:Panel(parent, "raised")
	row:SetColorToken("base")

	row.time = UI:Text(row, "", T.fontSize.sm, "", "textMuted")
	row.time:SetPoint("LEFT", T.space.sm, 0)
	row.time:SetWidth(46)

	row.side = UI:Text(row, "", T.fontSize.xs, "", "textMuted")
	row.side:SetPoint("LEFT", row.time, "RIGHT", T.space.xs, 0)
	row.side:SetWidth(30)

	row.name = UI:Text(row, "", T.fontSize.base, "", "textPrimary")
	row.name:SetPoint("LEFT", row.side, "RIGHT", T.space.sm, 0)
	row.name:SetWidth(110)

	row.price = UI:Text(row, "", T.fontSize.base, "", "gold", "RIGHT")
	row.price:SetPoint("LEFT", row.name, "RIGHT", T.space.sm, 0)
	row.price:SetWidth(90)

	-- Remove this one observation; ignore the advertiser entirely.
	row.ignoreBtn = UI:Button(row, "!", function()
		if row.obs then PP.Market:Ignore(row.obs.name) end
	end, "ghost", 22, 18)
	row.ignoreBtn:SetPoint("RIGHT", -T.space.sm, 0)
	UI:Tooltip(row.ignoreBtn, "Ignore this advertiser", function()
		return {
			("Forget everything %s has posted and never count them again."):format(
				row.obs and row.obs.name or "them"),
			" ",
			"For traders whose listings happen to mention a portal.",
		}
	end)

	row.removeBtn = UI:Button(row, "X", function()
		-- Pass the observation itself, not its id: identity always matches, including for
		-- records saved before observations carried ids.
		if row.obs then PP.Market:Remove(row.obs) end
	end, "ghost", 22, 18)
	row.removeBtn:SetPoint("RIGHT", row.ignoreBtn, "LEFT", -2, 0)
	UI:Tooltip(row.removeBtn, "Remove this advert", { "Drops just this line from the figures." })

	row.text = UI:Text(row, "", T.fontSize.sm, "", "textMuted")
	row.text:SetPoint("LEFT", row.price, "RIGHT", T.space.md, 0)
	row.text:SetPoint("RIGHT", row.removeBtn, "LEFT", -T.space.sm, 0)

	-- The row is too narrow for a full advert, so the whole thing lives in the tooltip.
	-- Judging whether a line belongs in your pricing needs the entire message.
	UI:Tooltip(row, function() return row.obs and row.obs.name or "" end, function()
		if not row.obs then return {} end
		return {
			row.obs.text or "",
			" ",
			("%s %s  ·  %s  ·  %s ago"):format(
				row.obs.side == "ask" and "Asking" or "Offering",
				U.MoneyPlain(row.obs.copper),
				row.obs.channel or "?",
				U.Ago(row.obs.ts)),
		}
	end, "ANCHOR_TOP")

	UI:Hoverable(row, "base", "raised")
	return row
end

local function updateObsRow(row, obs)
	row.obs = obs
	if not obs then return end
	row.time:SetText(U.Ago(obs.ts))
	row.name:SetText(obs.name or "?")
	row.price:SetText(U.MoneyFull(obs.copper))

	if obs.side == "ask" then
		row.side:SetText("ASK")
		row.side:SetTextColor(T:C("warning"))
	else
		row.side:SetText("BID")
		row.side:SetTextColor(T:C("positive"))
	end

	row.text:SetText(obs.text or "")
end

--------------------------------------------------------------------------------
-- Tab
--------------------------------------------------------------------------------

local function build(parent)
	local frame = CreateFrame("Frame", nil, parent)
	frame.window = 3600

	local bar = CreateFrame("Frame", nil, frame)
	bar:SetPoint("TOPLEFT", T.space.lg, -T.space.lg)
	bar:SetPoint("TOPRIGHT", -T.space.lg, -T.space.lg)
	bar:SetHeight(T.size.buttonHeight)

	local heading = UI:Text(bar, L["Market"], T.fontSize.lg, "", "textPrimary")
	heading:SetPoint("LEFT")

	local clear = UI:Button(bar, "Clear data", function()
		PP.Market:Clear()
	end, "ghost", 80, 24)
	clear:SetPoint("RIGHT")

	local segmented = UI:Segmented(bar, WINDOWS, 3600, function(value)
		frame.window = value
		frame.__def.refresh(frame)
	end)
	segmented:SetPoint("RIGHT", clear, "LEFT", -T.space.md, 0)
	segmented:SetWidth(300)

	-- Headline tiles
	local tiles = {}
	local specs = {
		{ key = "ask",  label = "Competition asking" },
		{ key = "mine", label = "Your price" },
		{ key = "bid",  label = "Customers offering" },
	}
	local prev
	for _, spec in ipairs(specs) do
		local tile = UI:StatTile(frame, spec.label, "-")
		tile:SetHeight(90)
		if prev then
			tile:SetPoint("TOPLEFT", prev, "TOPRIGHT", T.space.md, 0)
		else
			tile:SetPoint("TOPLEFT", bar, "BOTTOMLEFT", 0, -T.space.md)
		end
		tiles[spec.key] = tile
		prev = tile
	end
	frame.tiles = tiles

	frame:SetScript("OnSizeChanged", function(self)
		local available = self:GetWidth() - T.space.lg * 2 - T.space.md * 2
		local width = available / 3
		for _, tile in pairs(tiles) do tile:SetWidth(width) end
	end)

	-- Per-destination comparison
	local compareLabel = UI:Label(frame, "Your pricing vs market")
	compareLabel:SetPoint("TOPLEFT", tiles.ask, "BOTTOMLEFT", 0, -T.space.lg)

	local head = CreateFrame("Frame", nil, frame)
	head:SetPoint("TOPLEFT", compareLabel, "BOTTOMLEFT", 0, -T.space.xs)
	head:SetPoint("RIGHT", frame, "RIGHT", -T.space.lg, 0)
	head:SetHeight(14)

	local hMine = UI:Label(head, "Yours")
	hMine:SetPoint("LEFT", 134, 0)
	hMine:SetWidth(96)
	hMine:SetJustifyH("RIGHT")

	local hMarket = UI:Label(head, "Market median")
	hMarket:SetPoint("LEFT", 134 + 96 + T.space.md, 0)
	hMarket:SetWidth(96)
	hMarket:SetJustifyH("RIGHT")

	frame.destRows = {}
	local anchor = head
	for i, dest in ipairs(PP.Dest:Available()) do
		local row = createDestRow(frame)
		row:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, i == 1 and -T.space.xs or 0)
		row:SetPoint("RIGHT", frame, "RIGHT", -T.space.lg, 0)
		frame.destRows[i] = { row = row, dest = dest }
		anchor = row
	end
	frame.lastDestRow = anchor

	-- Recent observations
	local obsLabel = UI:Label(frame, "Recent adverts seen")
	obsLabel:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -T.space.lg)
	frame.obsLabel = obsLabel

	local ignoredLabel = UI:Text(frame, "", T.fontSize.sm, "", "textMuted", "RIGHT")
	ignoredLabel:SetPoint("RIGHT", frame, "RIGHT", -T.space.lg - 60, 0)
	ignoredLabel:SetPoint("TOP", obsLabel, "TOP", 0, 2)
	frame.ignoredLabel = ignoredLabel

	local resetIgnored = UI:Button(frame, "Reset", function()
		PP.Market:ClearIgnored()
	end, "ghost", 52, 18)
	resetIgnored:SetPoint("RIGHT", frame, "RIGHT", -T.space.lg, 0)
	resetIgnored:SetPoint("TOP", obsLabel, "TOP", 0, 3)
	frame.resetIgnored = resetIgnored
	UI:Tooltip(resetIgnored, "Reset ignored advertisers",
		{ "Start counting everyone again." })

	local list = UI:ScrollList(frame, OBS_HEIGHT, createObsRow, updateObsRow, 1)
	list:SetPoint("TOPLEFT", obsLabel, "BOTTOMLEFT", 0, -T.space.sm)
	list:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -T.space.lg, T.space.lg)
	list:SetEmptyState("No competitor adverts seen yet",
		"Sit in a city with Trade chat enabled and they'll appear here.")
	frame.list = list

	return frame
end

local function refresh(frame)
	local window = frame.window
	local summary = PP.Market:Summary(window)

	-- Competition
	if summary.ask.median then
		frame.tiles.ask:SetValue(U.MoneyFull(summary.ask.median), "warning")
		frame.tiles.ask:SetSub(("%d mage%s · %s to %s"):format(
			summary.ask.advertisers,
			summary.ask.advertisers == 1 and "" or "s",
			U.MoneyPlain(summary.ask.low), U.MoneyPlain(summary.ask.high)))
	else
		frame.tiles.ask:SetValue("-", "textMuted")
		frame.tiles.ask:SetSub("no adverts seen in this window")
	end

	-- Yours, judged against the competition
	local defaultPrice = PP.profile.prices.default or 0
	frame.tiles.mine:SetValue(U.MoneyFull(defaultPrice), "textPrimary")
	if summary.ask.median then
		local delta = defaultPrice - summary.ask.median
		if math.abs(delta) <= summary.ask.median * 0.05 then
			frame.tiles.mine:SetSub("in line with the market")
		elseif delta < 0 then
			frame.tiles.mine:SetSub(("undercutting by %s"):format(U.MoneyPlain(-delta)))
		else
			frame.tiles.mine:SetSub(("%s above the median"):format(U.MoneyPlain(delta)))
		end
	else
		frame.tiles.mine:SetSub("nothing to compare against yet")
	end

	-- Demand
	if summary.bid.median then
		frame.tiles.bid:SetValue(U.MoneyFull(summary.bid.median), "positive")
		frame.tiles.bid:SetSub(("%d customer%s named a price"):format(
			summary.bid.advertisers, summary.bid.advertisers == 1 and "" or "s"))
	else
		frame.tiles.bid:SetValue("-", "textMuted")
		frame.tiles.bid:SetSub("no customer offers seen")
	end

	for _, entry in ipairs(frame.destRows) do
		updateDestRow(entry.row, entry.dest, window)
	end

	-- Newest first.
	local observations = PP.Market:Observations(window)
	U.SortBy(observations, function(o) return o.ts end, true)
	frame.list:SetData(observations)
	frame.obsLabel:SetText(("RECENT ADVERTS SEEN (%d)"):format(#observations))

	local ignored = PP.Market:IgnoredCount()
	frame.ignoredLabel:SetText(ignored > 0
		and ("%d advertiser%s ignored"):format(ignored, ignored == 1 and "" or "s")
		or "")
	frame.resetIgnored:SetShown(ignored > 0)
end

UI:RegisterTab({
	key = "market",
	label = L["Market"],
	icon = "Interface\\ICONS\\INV_Misc_Note_01",
	order = 4,
	build = build,
	refresh = refresh,
})
