-- Portal Pro :: Modules/Whispers.lua
-- Whispers are NOT hardware-event gated (unlike SAY/YELL/CHANNEL — see PLAN.md §1.1),
-- so this is the one channel the addon can genuinely automate.

local ADDON_NAME, PP = ...
local U, C = PP.U, PP.C

local W = PP:NewModule("Whispers")
PP.Whispers = W

local MAX_LEN = 255
local MIN_GAP = 3            -- seconds between whispers to the same person
local lastSent = {}

--------------------------------------------------------------------------------
-- Template expansion
--------------------------------------------------------------------------------

-- Only {word} tokens we know are substituted. Anything else — notably raid icon
-- tokens like {rt1} — passes through untouched so it still renders in chat.
function W:Expand(template, ctx)
	if not template or template == "" then return "" end
	ctx = ctx or {}

	local dest = ctx.dest
	if not dest and ctx.destKey then dest = PP.Dest:ByKey(ctx.destKey) end

	local price = ctx.price
	if not price then
		price = dest and PP.Dest:Price(dest) or (PP.profile and PP.profile.prices.default) or 0
	end

	local map = {
		name  = ctx.name or "friend",
		dest  = dest and dest.name or "your city",
		short = dest and dest.short or "",
		city  = GetZoneText() or "the city",
		price = U.MoneyPlain(price),
		me    = PP.playerName or UnitName("player") or "",
		count = tostring((PP.global and PP.global.counters.casts) or 0),
	}

	return (template:gsub("{(%w+)}", function(key)
		local value = map[key:lower()]
		if value == nil then return "{" .. key .. "}" end
		return value
	end))
end

--------------------------------------------------------------------------------
-- Sending
--------------------------------------------------------------------------------

function W:Send(target, message)
	if not target or target == "" or not message or message == "" then return false end

	local now = GetTime()
	local last = lastSent[target]
	if last and (now - last) < MIN_GAP then
		PP:Debug("whisper throttled for", target)
		return false
	end

	if #message > MAX_LEN then
		message = message:sub(1, MAX_LEN)
		PP:Warn("Whisper truncated to 255 characters.")
	end

	lastSent[target] = now
	SendChatMessage(message, "WHISPER", nil, target)
	PP.global.counters.whispers = (PP.global.counters.whispers or 0) + 1
	PP:Fire("WHISPER_SENT", target, message)
	return true
end

--- @param key string one of PP.profile.whispers
function W:SendTemplate(key, target, ctx)
	local template = PP.profile and PP.profile.whispers[key]
	if not template or template == "" then
		PP:Debug("no whisper template for", key)
		return false
	end
	ctx = ctx or {}
	ctx.name = ctx.name or target
	return self:Send(target, self:Expand(template, ctx))
end

function W:Preview(key, ctx)
	local template = PP.profile and PP.profile.whispers[key] or ""
	return self:Expand(template, ctx or { name = "Playername", destKey = "stormwind" })
end

--------------------------------------------------------------------------------
-- The greeting library
--------------------------------------------------------------------------------
--
-- The seven templates above are *roles*: there is exactly one "thanks". These are
-- alternatives for one role - the opening line - because the right thing to open with depends
-- on which ad brought them in. An ad advertising tips wants a different first message from one
-- quoting a fixed price, and having to remember to edit the greeting before each broadcast is
-- exactly the kind of thing nobody remembers.
--
-- Entries carry a stable `id`. Referencing them by name would repoint an ad on rename;
-- referencing by position would repoint every ad below a deletion.

function W:Library()
	return PP.profile.whispers.library
end

--- @return table|nil entry
function W:Greeting(id)
	if not id then return nil end
	for _, entry in ipairs(W:Library()) do
		if entry.id == id then return entry end
	end
	return nil
end

function W:AddGreeting(name, text)
	local whispers = PP.profile.whispers
	local entry = {
		id   = whispers.nextId or 1,
		name = (name and name ~= "") and name or ("Greeting " .. (whispers.nextId or 1)),
		text = text or PP.profile.whispers.greeting or "",
	}
	whispers.nextId = entry.id + 1
	table.insert(whispers.library, entry)

	PP:Fire("GREETINGS_CHANGED")
	return entry
end

--- Removing a greeting has to clear every ad pointing at it, or those ads silently fall back
--- to the default with no indication that the thing they were using is gone.
function W:DeleteGreeting(id)
	local library = W:Library()
	for i = #library, 1, -1 do
		if library[i].id == id then table.remove(library, i) end
	end

	for _, ad in ipairs(PP.profile.ads.list) do
		if ad.greetingId == id then ad.greetingId = nil end
	end

	PP:Fire("GREETINGS_CHANGED")
	PP:Fire("ADS_CHANGED")
end

--- Which opening line to use for this lead, and why.
---
--- Falls back to the shared greeting whenever the ad has no assignment, the assignment has been
--- deleted, or nothing could be attributed - so this can never return an empty message.
--- @return string template, table|nil entry, table|nil ad
function W:GreetingFor(lead)
	local ad = PP.Broadcaster:AdForLead(lead)

	if ad and ad.greetingId then
		local entry = W:Greeting(ad.greetingId)
		if entry and entry.text and entry.text ~= "" then
			return entry.text, entry, ad
		end
	end

	return PP.profile.whispers.greeting or "", nil, ad
end

--- The greeting path, as distinct from SendTemplate("greeting"), because this one resolves
--- which ad the person is answering first.
function W:SendGreeting(target, ctx)
	local template, entry, ad = W:GreetingFor(ctx)
	if not template or template == "" then
		PP:Debug("no greeting to send")
		return false
	end

	ctx = ctx or {}
	ctx.name = ctx.name or target

	PP:Debug("greeting via", entry and entry.name or "default",
		"for ad", ad and ad.name or "none")
	return W:Send(target, W:Expand(template, ctx))
end

--------------------------------------------------------------------------------
-- Auto-reply
--------------------------------------------------------------------------------

-- A whisper that scores as a portal request gets the greeting automatically when
-- auto-whisper is on. Leads already created the card; this just answers it.
function W:OnEnable()
	PP:On("LEAD_ADDED", function(lead)
		if lead.channel ~= "WHISPER" then return end
		local cfg = PP.profile.detection
		-- Leads:Add already fired the greeting if autoWhisper is on; don't double up.
		if cfg.autoWhisper then return end
		PP:Debug("whisper lead awaiting manual reply:", lead.name)
	end)
end

--- Ordered list of template keys for the quick-reply buttons on a lead card.
W.TEMPLATE_ORDER = {
	{ key = "greeting", label = "Greet" },
	{ key = "quote",    label = "Quote" },
	{ key = "location", label = "Where" },
	{ key = "invited",  label = "Invited" },
	{ key = "portalUp", label = "Up" },
	{ key = "thanks",   label = "Thanks" },
	{ key = "busy",     label = "Busy" },
}
