-- Portal Pro :: Modules/Destinations.lua
-- The six Classic Era cities, their spell IDs, and every way a customer might spell them.

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

local D = PP:NewModule("Destinations")
PP.Dest = D

-- Aliases are matched as whole words against a normalised message, which is what makes
-- "if" (Ironforge) safe to include: it only fires as a standalone token, never inside
-- ordinary English. Longest match wins when several cities hit.
D.list = {
	{
		key = "stormwind", name = "Stormwind", short = "SW", faction = "Alliance",
		teleport = 3561, portal = 10059,
		aliases = { "sw", "stormwind", "storm wind", "swind", "stormwing", "sotrmwind" },
	},
	{
		key = "ironforge", name = "Ironforge", short = "IF", faction = "Alliance",
		teleport = 3562, portal = 11416,
		aliases = { "if", "ironforge", "iron forge", "ironforce", "forge", "ifo" },
	},
	{
		key = "darnassus", name = "Darnassus", short = "Darn", faction = "Alliance",
		teleport = 3565, portal = 11419,
		aliases = { "darn", "darnassus", "darnasus", "darna", "dara", "teldrassil", "tel", "dn" },
	},
	{
		key = "orgrimmar", name = "Orgrimmar", short = "Org", faction = "Horde",
		teleport = 3567, portal = 11417,
		aliases = { "og", "org", "orgrimmar", "ogrimmar", "orgri", "orgrim", "ogri" },
	},
	{
		key = "undercity", name = "Undercity", short = "UC", faction = "Horde",
		teleport = 3563, portal = 11418,
		aliases = { "uc", "undercity", "under city", "ud", "underc" },
	},
	{
		key = "thunderbluff", name = "Thunder Bluff", short = "TB", faction = "Horde",
		teleport = 3566, portal = 11420,
		aliases = { "tb", "thunder bluff", "thunderbluff", "bluff", "tbluff", "thunder" },
	},
}

local byKey, bySpell = {}, {}
for _, dest in ipairs(D.list) do
	byKey[dest.key] = dest
	bySpell[dest.portal]   = { dest = dest, kind = "portal" }
	bySpell[dest.teleport] = { dest = dest, kind = "teleport" }
end

--------------------------------------------------------------------------------
-- Lookups
--------------------------------------------------------------------------------

function D:ByKey(key)
	return byKey[key or ""]
end

--- @return table|nil dest, string|nil kind ("portal"|"teleport")
function D:BySpellID(spellID)
	local hit = bySpell[spellID]
	if not hit then return nil, nil end
	return hit.dest, hit.kind
end

function D:IsPortalSpell(spellID)
	return bySpell[spellID] ~= nil
end

--- Find the destination referenced in a message.
---
--- Position beats length. "wtb port to UC from Org" names two cities: longest-alias-wins
--- picks "org" over "uc" and quotes entirely the wrong city, because it has no idea one of
--- them is where the customer already IS. So:
---   1. A city directly after "to" is where they want to go. It wins outright.
---   2. A city directly after "from" is their origin. It's excluded from the fallback.
---   3. Otherwise, longest alias wins, so "darnassus" doesn't lose to a stray "dar".
--- @param normalized string output of U.Normalize
function D:Resolve(normalized)
	if not normalized or normalized == "" then return nil end
	local padded = " " .. normalized .. " "

	local afterTo, afterToLen
	local best, bestLen
	local origin

	for _, dest in ipairs(D.list) do
		for _, alias in ipairs(dest.aliases) do
			local token = " " .. alias .. " "
			if padded:find(token, 1, true) then
				local isTo   = padded:find(" to" .. token, 1, true) ~= nil
				local isFrom = padded:find(" from" .. token, 1, true) ~= nil

				if isTo and (not afterToLen or #alias > afterToLen) then
					afterTo, afterToLen = dest, #alias
				end
				if isFrom then
					origin = origin or dest
				elseif not bestLen or #alias > bestLen then
					best, bestLen = dest, #alias
				end
			end
		end
	end

	-- Fall back to the origin only if it's the sole city mentioned: "port from org" with
	-- nothing else is still a better guess than nothing.
	return afterTo or best or origin
end

--- How many distinct cities a message names. Used to tell a mage's per-city price list
--- ("SW 10g, IF 10g, Darn 12g") from a goods shop listing several unrelated items.
function D:CountMentioned(normalized)
	if not normalized or normalized == "" then return 0 end
	local padded = " " .. normalized .. " "
	local count = 0
	for _, dest in ipairs(D.list) do
		for _, alias in ipairs(dest.aliases) do
			if padded:find(" " .. alias .. " ", 1, true) then
				count = count + 1
				break
			end
		end
	end
	return count
end

--------------------------------------------------------------------------------
-- Faction
--------------------------------------------------------------------------------

function D:PlayerFaction()
	return (UnitFactionGroup("player"))
end

function D:IsReachable(dest)
	if not dest then return false end
	return dest.faction == D:PlayerFaction()
end

--- Destinations the player can actually service, in list order.
function D:Available()
	local faction = D:PlayerFaction()
	local out = {}
	for _, dest in ipairs(D.list) do
		if dest.faction == faction then out[#out + 1] = dest end
	end
	return out
end

--------------------------------------------------------------------------------
-- Icons & pricing
--------------------------------------------------------------------------------

function D:Icon(dest)
	if not dest then return 134400 end -- question mark
	return C.SpellIcon(dest.portal) or C.SpellIcon(dest.teleport) or 134400
end

--- Price in copper for a destination, falling back to the profile default.
function D:Price(destOrKey)
	local key = type(destOrKey) == "table" and destOrKey.key or destOrKey
	local prices = PP.profile and PP.profile.prices
	if not prices then return 0 end
	local explicit = key and prices[key]
	if explicit and explicit > 0 then return explicit end
	return prices.default or 0
end

function D:SetPrice(destOrKey, copper)
	local key = type(destOrKey) == "table" and destOrKey.key or destOrKey
	if not key or not PP.profile then return end
	PP.profile.prices[key] = (copper and copper > 0) and copper or nil
	PP:Fire("PRICES_CHANGED")
end

--- Does the player know the portal (or at least the teleport) for this city?
function D:IsKnown(dest)
	if not dest then return false end
	return C.IsSpellKnown(dest.portal) or C.IsSpellKnown(dest.teleport)
end
