-- Portal Pro :: UI/Tab_Ads.lua
-- Ad composer. The preview renders {rt1}-style tokens exactly as chat will show them,
-- so you find out here rather than after posting to Trade.

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

local MAX_LEN = 255

local CHANNELS = {
	{ key = "TRADE",   label = "Trade" },
	{ key = "GENERAL", label = "General" },
	{ key = "LFG",     label = "LookingForGroup" },
	{ key = "SAY",     label = "Say" },
	{ key = "YELL",    label = "Yell" },
}

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

	--------------------------------------------------------------------------
	-- Left: ad list
	--------------------------------------------------------------------------
	local sidebar = CreateFrame("Frame", nil, frame)
	sidebar:SetPoint("TOPLEFT", T.space.lg, -T.space.lg)
	sidebar:SetPoint("BOTTOMLEFT", T.space.lg, T.space.lg)
	sidebar:SetWidth(150)

	local listLabel = UI:Label(sidebar, L["Ads"])
	listLabel:SetPoint("TOPLEFT")

	local newBtn = UI:Button(sidebar, "+ " .. L["New ad"], function()
		PP.Broadcaster:NewAd()
	end, "secondary", 150, 24)
	newBtn:SetPoint("BOTTOMLEFT")

	local adList = UI:ScrollList(sidebar, 30,
		function(listParent)
			local row = UI:Panel(listParent, "raised")
			UI:Border(row, "borderSubtle")
			row.text = UI:Text(row, "", T.fontSize.base, "", "textSecondary")
			row.text:SetPoint("LEFT", T.space.sm, 0)
			row.text:SetPoint("RIGHT", -T.space.sm, 0)
			row:EnableMouse(true)
			row:SetScript("OnMouseDown", function(self)
				if self.index then PP.Broadcaster:Select(self.index) end
			end)
			return row
		end,
		function(row, ad, index)
			row.index = index
			row.text:SetText(ad.name or ("Ad " .. index))
			local active = (PP.profile.ads.selected == index)
			row:SetColorToken(active and "accentMuted" or "raised")
			row.text:SetTextColor(T:C(active and "accent" or "textSecondary"))
			row:SetBorderColor(active and "accent" or "borderSubtle")
		end,
		T.space.xs)
	adList:SetPoint("TOPLEFT", listLabel, "BOTTOMLEFT", 0, -T.space.sm)
	adList:SetPoint("BOTTOMRIGHT", newBtn, "TOPRIGHT", 0, T.space.sm)
	frame.adList = adList

	--------------------------------------------------------------------------
	-- Right: editor
	--------------------------------------------------------------------------
	local editor = CreateFrame("Frame", nil, frame)
	editor:SetPoint("TOPLEFT", sidebar, "TOPRIGHT", T.space.lg, 0)
	editor:SetPoint("BOTTOMRIGHT", -T.space.lg, T.space.lg)

	local nameLabel = UI:Label(editor, L["Ad name"])
	nameLabel:SetPoint("TOPLEFT")

	-- Edits are written to the ad the editor is actually showing (frame.editingAd), not to
	-- whatever happens to be selected when the field commits. Those differ the moment the
	-- selection moves between typing and committing, and the text then lands on the wrong ad.
	local nameInput = UI:Input(editor, "Name this ad", "", function(value)
		local ad = frame.editingAd
		if ad and ad.name ~= value then
			ad.name = value
			PP:Fire("ADS_CHANGED")
		end
	end)
	-- Commit per keystroke like the message box does, so there is no pending edit to lose
	-- on a reload or a click elsewhere.
	nameInput.liveUpdate = true
	nameInput:SetPoint("TOPLEFT", nameLabel, "BOTTOMLEFT", 0, -T.space.xs)
	nameInput:SetWidth(240)
	frame.nameInput = nameInput

	-- Arm-then-confirm, matching the ledger. This sits next to the name field and used to
	-- destroy an ad on a single stray click with no undo.
	local deleteBtn
	deleteBtn = UI:Button(editor, L["Delete ad"], function(self)
		if frame.deleteArmed then
			frame.deleteArmed = false
			local _, index = PP.Broadcaster:Selected()
			PP.Broadcaster:DeleteAd(index)
			return
		end
		frame.deleteArmed = true
		self:SetTextValue("Sure?")
		C_Timer.After(3, function()
			if frame.deleteArmed then
				frame.deleteArmed = false
				self:SetTextValue(L["Delete ad"])
			end
		end)
	end, "danger", 90, T.size.inputHeight)
	deleteBtn:SetPoint("LEFT", nameInput, "RIGHT", T.space.sm, 0)
	frame.deleteBtn = deleteBtn

	-- Message
	local msgLabel = UI:Label(editor, L["Message"])
	msgLabel:SetPoint("TOPLEFT", nameInput, "BOTTOMLEFT", 0, -T.space.md)

	local counter = UI:Text(editor, "", T.fontSize.xs, "", "textMuted", "RIGHT")
	counter:SetPoint("TOPRIGHT", nameInput, "BOTTOMRIGHT", 0, -T.space.md)
	counter:SetPoint("RIGHT", editor, "RIGHT", 0, 0)
	frame.counter = counter

	local msgInput = UI:TextArea(editor, "", function(value)
		local ad = frame.editingAd
		if ad and ad.text ~= value then
			ad.text = value
			PP:Fire("ADS_CHANGED")
		end
	end, 56)
	msgInput:SetPoint("TOPLEFT", msgLabel, "BOTTOMLEFT", 0, -T.space.xs)
	msgInput:SetPoint("RIGHT", editor, "RIGHT")
	frame.msgInput = msgInput

	-- Icon / colour picker
	local picker = UI:IconPicker(editor, function(token)
		frame.msgInput:InsertAtCursor(token)
	end)
	picker:SetPoint("TOPLEFT", msgInput, "BOTTOMLEFT", 0, -T.space.sm)

	-- Preview
	local previewLabel = UI:Label(editor, L["Preview"])
	previewLabel:SetPoint("TOPLEFT", picker, "BOTTOMLEFT", 0, -T.space.md)

	local previewBox = UI:Panel(editor, "base")
	previewBox:SetPoint("TOPLEFT", previewLabel, "BOTTOMLEFT", 0, -T.space.xs)
	previewBox:SetPoint("RIGHT", editor, "RIGHT")
	previewBox:SetHeight(40)
	UI:Border(previewBox, "borderSubtle")

	local previewText = UI:Text(previewBox, "", T.fontSize.base, "", "textPrimary")
	previewText:SetPoint("LEFT", T.space.sm, 0)
	previewText:SetPoint("RIGHT", -T.space.sm, 0)
	previewText:SetWordWrap(true)
	frame.previewText = previewText

	-- Channels
	local chanLabel = UI:Label(editor, L["Channels"])
	chanLabel:SetPoint("TOPLEFT", previewBox, "BOTTOMLEFT", 0, -T.space.md)

	frame.channelSwitches = {}
	local x = 0
	for _, spec in ipairs(CHANNELS) do
		local sw = UI:Switch(editor, spec.label, false, function(value)
			local ad = frame.editingAd
			if ad then
				ad.channels = ad.channels or {}
				ad.channels[spec.key] = value or nil
				PP:Fire("ADS_CHANGED")
			end
		end)
		sw:SetPoint("TOPLEFT", chanLabel, "BOTTOMLEFT", x, -T.space.xs)
		frame.channelSwitches[spec.key] = sw
		x = x + sw:GetWidth() + T.space.md
	end

	-- Behaviour row
	local throttle = UI:Slider(editor, "Broadcast cooldown", 15, 300, 5,
		PP.profile.ads.throttle or 60,
		function(value) PP.profile.ads.throttle = math.floor(value) end,
		function(value) return ("%ds"):format(value) end)
	throttle:SetPoint("TOPLEFT", chanLabel, "BOTTOMLEFT", 0, -50)
	throttle:SetWidth(200)
	frame.throttleSlider = throttle

	local rotation = UI:Dropdown(editor, {
		{ value = "off",        label = "Always send selected" },
		{ value = "sequential", label = "Rotate in order" },
		{ value = "random",     label = "Rotate randomly" },
	}, PP.profile.ads.rotation or "off", function(value)
		PP.profile.ads.rotation = value
	end, 190)
	UI:Tooltip(rotation, "After broadcasting", {
		"Always send selected - the ad you've picked is the ad that goes out,",
		"every time.",
		" ",
		"The rotate options move the selection on after each send so you don't",
		"post identical text repeatedly. Useful, but it does mean the next",
		"Broadcast sends a different ad than the one you just chose.",
	})
	rotation:SetPoint("LEFT", throttle, "RIGHT", T.space.xl, -8)
	frame.rotationDropdown = rotation

	-- Which greeting answers this ad.
	--
	-- Writes to frame.editingAd, not to whatever is selected: the same rule as the name and
	-- message fields above, and for the same reason - the selection can move between opening
	-- the dropdown and choosing from it.
	local greetLabel = UI:Label(editor, "Greeting for this ad")
	greetLabel:SetPoint("TOPLEFT", throttle, "BOTTOMLEFT", 0, -T.space.md)

	local greeting = UI:Dropdown(editor, { { value = 0, label = "Default greeting" } }, 0,
		function(value)
			local ad = frame.editingAd
			if not ad then return end
			ad.greetingId = (value ~= 0) and value or nil
			PP:Fire("ADS_CHANGED")
		end, 230)
	greeting:SetPoint("TOPLEFT", greetLabel, "BOTTOMLEFT", 0, -T.space.xs)
	frame.greetingDropdown = greeting

	UI:Tooltip(greeting, "Greeting for this ad", {
		"Which opening line goes out when somebody answers this ad.",
		" ",
		"An ad asking for tips wants a different first message from one",
		"quoting a fixed price, and remembering to edit the greeting before",
		"each broadcast is exactly the thing nobody remembers.",
		" ",
		"Replies are matched to the ad you last broadcast - in that channel",
		"for a channel request, or anywhere for a whisper. If nothing recent",
		"can be matched, the default greeting is used.",
		" ",
		"Write the alternatives in Settings, under Whispers.",
	})

	local greetPreview = UI:Text(editor, "", T.fontSize.sm, "", "textMuted")
	greetPreview:SetPoint("TOPLEFT", greeting, "BOTTOMLEFT", 0, -T.space.xs)
	greetPreview:SetPoint("RIGHT", editor, "RIGHT")
	greetPreview:SetWordWrap(true)
	greetPreview:SetJustifyH("LEFT")
	frame.greetingPreview = greetPreview

	-- Send
	local sendBtn = UI:Button(editor, L["Broadcast"] .. " now", function()
		-- OnClick is a hardware event, which is what makes a CHANNEL send legal here.
		PP.Broadcaster:Broadcast()
	end, "primary", 130, 28)
	sendBtn:SetPoint("BOTTOMRIGHT")
	frame.sendBtn = sendBtn

	local armBtn = UI:Button(editor, "Arm", function()
		PP.Broadcaster:ToggleArm()
	end, "secondary", 70, 28)
	armBtn:SetPoint("RIGHT", sendBtn, "LEFT", -T.space.sm, 0)
	frame.armBtn = armBtn
	UI:Tooltip(armBtn, "Arm mode", {
		"Queue the ad and send it on your next keypress.",
		"Needs enabling in Settings first.",
		" ",
		"Blizzard requires a real input for Trade/General/Say/Yell -",
		"arming just means you don't have to aim for this button.",
	})

	local warning = UI:Text(editor, "", T.fontSize.sm, "", "warning")
	warning:SetPoint("BOTTOMLEFT", 0, 6)
	warning:SetPoint("RIGHT", armBtn, "LEFT", -T.space.md, 0)
	frame.warning = warning

	-- Live market line. This is where you decide what to charge, so this is where the
	-- competition's asking price belongs.
	local market = UI:Text(editor, "", T.fontSize.sm, "", "textMuted")
	market:SetPoint("BOTTOMLEFT", warning, "TOPLEFT", 0, 4)
	market:SetPoint("RIGHT", armBtn, "LEFT", -T.space.md, 0)
	frame.marketLine = market
	UI:Tooltip(market, "Live market", {
		"Median asking price from rival adverts seen in the last hour,",
		"counted once per mage rather than once per message.",
		" ",
		"Harvested from adverts the lead scanner already reads. Full",
		"breakdown on the Market tab.",
	})

	return frame
end

local function refresh(frame)
	local ads = PP.Broadcaster:List()
	frame.adList:SetData(ads)
	frame.adList:SetEmptyState("No ads", "")

	local ad = PP.Broadcaster:Selected()
	-- The editor binds to this object, so every input handler writes to the ad on screen
	-- rather than re-resolving the selection at commit time.
	frame.editingAd = ad
	if not ad then return end

	if not frame.nameInput.edit:HasFocus() then
		frame.nameInput:SetValue(ad.name)
	end
	if not frame.msgInput.edit:HasFocus() then
		frame.msgInput:SetValue(ad.text)
	end

	local expanded = PP.Broadcaster:Expand(ad.text or "")
	frame.previewText:SetText(UI:RenderChatPreview(expanded, 14))

	local length = #expanded
	frame.counter:SetText(("%d / %d %s"):format(length, MAX_LEN, L["characters"]))
	frame.counter:SetTextColor(T:C(length > MAX_LEN and "negative" or "textMuted"))

	for key, sw in pairs(frame.channelSwitches) do
		sw:SetValue((ad.channels and ad.channels[key]) and true or false, true)
	end

	frame.throttleSlider:SetValue(PP.profile.ads.throttle or 60)
	frame.rotationDropdown:SetValue(PP.profile.ads.rotation or "off", true)

	-- Rebuilt every refresh, not once at build: the library is edited on another tab, and a
	-- dropdown that still lists a greeting you deleted is worse than one that's a frame late.
	local items = { { value = 0, label = "Default greeting" } }
	for _, entry in ipairs(PP.Whispers:Library()) do
		items[#items + 1] = { value = entry.id, label = entry.name }
	end
	frame.greetingDropdown:SetItems(items)
	frame.greetingDropdown:SetValue(ad.greetingId or 0, true)

	local entry = ad.greetingId and PP.Whispers:Greeting(ad.greetingId)
	if entry then
		frame.greetingPreview:SetText("|cff6E7681Sends:|r "
			.. PP.Whispers:Expand(entry.text, { name = "Playername", destKey = "stormwind" }))
	elseif #PP.Whispers:Library() == 0 then
		frame.greetingPreview:SetText(
			"|cff6E7681Write alternatives in Settings > Whispers to assign one here.|r")
	else
		frame.greetingPreview:SetText("|cff6E7681Uses the shared greeting from Settings.|r")
	end

	-- Reset a half-armed delete if the selection moved.
	if frame.deleteArmed and frame.armedFor ~= ad then
		frame.deleteArmed = false
		frame.deleteBtn:SetTextValue(L["Delete ad"])
	end
	frame.armedFor = ad

	-- Market context for the price you're about to advertise.
	local stats = PP.Market:Stats(3600, "ask")
	if stats.median then
		local mine = PP.profile.prices.default or 0
		local delta = mine - stats.median
		local verdict, token
		if math.abs(delta) <= stats.median * 0.05 then
			verdict, token = "in line", "accent"
		elseif delta < 0 then
			verdict, token = ("undercutting by %s"):format(U.MoneyPlain(-delta)), "positive"
		else
			verdict, token = ("%s above market"):format(U.MoneyPlain(delta)), "warning"
		end
		frame.marketLine:SetText(("Trade median %s from %d mage%s  ·  you're %s"):format(
			U.MoneyPlain(stats.median), stats.advertisers,
			stats.advertisers == 1 and "" or "s", verdict))
		frame.marketLine:SetTextColor(T:C(token))
	else
		frame.marketLine:SetText("No rival adverts seen in the last hour.")
		frame.marketLine:SetTextColor(T:C("textMuted"))
	end

	local ok, problem = PP.Broadcaster:Validate(ad)
	frame.warning:SetText(ok and "" or problem)
	frame.sendBtn:SetEnabledState(ok and PP.Broadcaster:IsReady())
	frame.armBtn:SetTextValue(PP.Broadcaster:IsArmed() and "Disarm" or "Arm")
end

UI:RegisterTab({
	key = "ads",
	label = L["Ads"],
	icon = "Interface\\ICONS\\INV_Scroll_03",
	order = 2,
	build = build,
	refresh = refresh,
})
