-- KiraUI-Plugin_KeyVendor.lua
-- Keystone-vendor merchant skin.
-- At the M+ keystone vendor, the useful buys get highlighted and renamed short:
--   * 'Keystone Container'                  -> 'Keystone'        (blue)
--   * 'Set Keystone Map: <dungeon>'         -> short dungeon name (red)
--   * 'Set Keystone Level: <n>' in range    -> just the number    (green)
--   * 'Add Keystone Affix: <affix>'         -> short affix name   (blue)
-- Everything else is dimmed + desaturated. All other vendors are untouched.
--
-- Improvements over the WA:
--   * season-proof: dungeons/affixes are matched by the stable item-name
--     PREFIX ('Set Keystone Map:' etc.), not a hardcoded name list - new
--     seasons highlight automatically (short names fall back to full names
--     until added to MAP_SHORT below)
--   * vendor detection falls back to the items on sale (2+ keystone-map
--     entries = the keystone vendor) when the npc GUID is unreadable
--   * original fonts/colors are saved per button and restored exactly
--   * /kirakv dumps the open vendor's NPC ID + item list (and gossip
--     option IDs) so new-season IDs can be read straight from chat

local addonName, ns = ...
local _G = _G

local CreateFrame = CreateFrame
local hooksecurefunc = hooksecurefunc
local issecretvalue = issecretvalue
local pairs, ipairs, type, tonumber = pairs, ipairs, type, tonumber
local strsplit = strsplit
local mmin = math.min

ns.hasKeyVendor = true

local function secret(v) return issecretvalue and issecretvalue(v) end

----------------------------------------------------------------------
-- Settings
----------------------------------------------------------------------
local DEFAULTS = {
	enable = false,
	npcID = 123227,   -- MDI keystone vendor; 0 = detect by items on sale only
	minLevel = 17,    -- 'Set Keystone Level' range highlighted green
	maxLevel = 25,
	textSize = 18,    -- highlighted keystone/level/affix entries
	mapSize = 13,     -- highlighted dungeon names (smaller so they fit the box)
}

local function GetCfg() return ns.ModuleCfg('keyVendor', DEFAULTS) end
ns.KVGetCfg = GetCfg

----------------------------------------------------------------------
-- Classification (colors from the WA)
----------------------------------------------------------------------
local COLOR_UTIL = { 0x27 / 255, 0x9d / 255, 0xff / 255 } -- #279dff keystone + affixes
local COLOR_MAP  = { 0xff / 255, 0x16 / 255, 0x54 / 255 } -- #ff1654 dungeons
local COLOR_LVL  = { 0x3b / 255, 0xb2 / 255, 0x73 / 255 } -- #3bb273 level in range

-- Short names for this season's maps/affixes; anything not listed shows its
-- full name (still highlighted) until added here.
local MAP_SHORT = {
	["Eco-Dome Al'dani"] = 'Eco-Dome',
	['Operation: Floodgate'] = 'Floodgate',
	['Ara-Kara, City of Echoes'] = 'Ara-Kara',
	['Priory of the Sacred Flame'] = 'Priory',
	['Tazavesh: Streets of Wonder'] = 'Streets',
	["Tazavesh: So'leah's Gambit"] = 'Gambit',
	['Halls of Atonement'] = 'Halls',
	["Kings' Rest"] = 'Kings',
	['Ruby Life Pools'] = 'Ruby',
	['Temple of Sethraliss'] = 'Temple',
}
local AFFIX_SHORT = {
	["Xal'atath's Guile"] = 'Guile',
}

-- itemName -> rename (nil = keep), color (nil = dim)
local function Classify(name)
	if name == 'Keystone Container' then return 'Keystone', COLOR_UTIL end
	local lvl = name:match('^Set Keystone Level: (%d+)$')
	if lvl then
		local n = tonumber(lvl)
		local cfg = GetCfg()
		local hot = n and n >= (cfg.minLevel or 17) and n <= (cfg.maxLevel or 25)
		return lvl, hot and COLOR_LVL or nil
	end
	local map = name:match('^Set Keystone Map: (.+)$')
	if map then return MAP_SHORT[map] or map, COLOR_MAP end
	local affix = name:match('^Add Keystone Affix: (.+)$')
	if affix then return AFFIX_SHORT[affix] or affix, COLOR_UTIL end
	return nil, nil
end

----------------------------------------------------------------------
-- Merchant item name, both API generations: 12.x C_MerchantFrame.GetItemInfo
-- returns a MerchantItemInfo table (.name), older clients had the
-- GetMerchantItemInfo global returning the name directly.
----------------------------------------------------------------------
local function MerchantItemName(index)
	local CMF = _G.C_MerchantFrame
	if CMF and CMF.GetItemInfo then
		local ok, info = pcall(CMF.GetItemInfo, index)
		local nm = ok and type(info) == 'table' and info.name or nil
		if nm ~= nil and not secret(nm) and type(nm) == 'string' then return nm end
		if CMF.GetItemInfo then return nil end -- new API present: don't mix generations
	end
	local nm = _G.GetMerchantItemInfo and _G.GetMerchantItemInfo(index)
	if nm ~= nil and not secret(nm) and type(nm) == 'string' then return nm end
	return nil
end

----------------------------------------------------------------------
-- Vendor gate: npc GUID when readable, else the items on sale
----------------------------------------------------------------------
local function IsKeyVendor()
	local cfg = GetCfg()
	local guid = _G.UnitGUID and _G.UnitGUID('npc')
	if guid and not secret(guid) and (cfg.npcID or 0) > 0 then
		local unitType, _, _, _, _, id = strsplit('-', guid)
		if unitType == 'Creature' then
			return tonumber(id or 0) == cfg.npcID
		end
	end
	-- GUID unreadable (or npcID set to 0): 2+ keystone-map items = key vendor
	local hits = 0
	local n = (_G.GetMerchantNumItems and _G.GetMerchantNumItems()) or 0
	for i = 1, mmin(n, 60) do
		local nm = MerchantItemName(i)
		if nm and nm:find('Set Keystone Map:', 1, true) then
			hits = hits + 1
			if hits >= 2 then return true end
		end
	end
	return false
end

----------------------------------------------------------------------
-- Skinning
----------------------------------------------------------------------
local skinnedActive = false

-- remember each button's stock look once, restore it exactly
local function SaveOrig(button)
	if button.__kiraKVOrig then return button.__kiraKVOrig end
	local o = {}
	if button.Name then
		local font, size, flags = button.Name:GetFont()
		local r, g, b = button.Name:GetTextColor()
		o.font, o.size, o.flags = font, size, flags
		o.r, o.g, o.b = r, g, b
	end
	button.__kiraKVOrig = o
	return o
end

local function RestoreButton(button)
	local o = button.__kiraKVOrig
	if not o then return end
	button:SetAlpha(1)
	if button.icon then button.icon:SetDesaturated(false) end
	if button.Name then
		if o.font then button.Name:SetFont(ns.GetFont(o.font), o.size or 12, o.flags) end
		button.Name:SetTextColor(o.r or 1, o.g or 0.82, o.b or 0)
	end
end

local function ResetMerchantButtons()
	if not skinnedActive then return end
	skinnedActive = false
	local per = _G.MERCHANT_ITEMS_PER_PAGE or 12
	for i = 1, per do
		local button = _G['MerchantItem' .. i]
		if button then RestoreButton(button) end
	end
end

local function SkinMerchant()
	local cfg = GetCfg()
	if not cfg.enable then ResetMerchantButtons() return end
	if not IsKeyVendor() then ResetMerchantButtons() return end
	skinnedActive = true
	local per = _G.MERCHANT_ITEMS_PER_PAGE or 12
	local page = (_G.MerchantFrame and _G.MerchantFrame.page) or 1
	for i = 1, per do
		local button = _G['MerchantItem' .. i]
		local index = i + (page - 1) * per
		local itemName = MerchantItemName(index)
		if button and itemName then
			local orig = SaveOrig(button)
			local rename, color = Classify(itemName)
			if rename and button.Name then button.Name:SetText(rename) end
			if color then
				button:SetAlpha(1)
				if button.icon then button.icon:SetDesaturated(false) end
				if button.Name then
					button.Name:SetTextColor(color[1], color[2], color[3])
					-- dungeon names get their own (smaller) size so long ones
					-- don't wrap out of the button
					local size = (color == COLOR_MAP) and (cfg.mapSize or 13) or (cfg.textSize or 18)
					if orig.font then button.Name:SetFont(ns.GetFont(orig.font), size, orig.flags) end
				end
			else
				button:SetAlpha(0.2)
				if button.icon then button.icon:SetDesaturated(true) end
				if button.Name then
					button.Name:SetTextColor(0.5, 0.5, 0.5)
					if orig.font then button.Name:SetFont(ns.GetFont(orig.font), orig.size or 12, orig.flags) end
				end
			end
		end
	end
end

-- Re-skin (or restore) the open vendor when settings change.
ns.KVApply = function()
	if _G.MerchantFrame and _G.MerchantFrame:IsShown() then
		pcall(SkinMerchant)
	elseif not GetCfg().enable then
		ResetMerchantButtons()
	end
end

----------------------------------------------------------------------
-- Hooks (installed once at load; pcall'd so a Blizzard merchant rework
-- can never error-spam mid-run, same policy as the MDI gossip module)
----------------------------------------------------------------------
if type(_G.MerchantFrame_Update) == 'function' then
	hooksecurefunc('MerchantFrame_Update', function() pcall(SkinMerchant) end)
end
if _G.MerchantFrame and _G.MerchantFrame.HookScript then
	_G.MerchantFrame:HookScript('OnHide', function() pcall(ResetMerchantButtons) end)
end

----------------------------------------------------------------------
-- Diagnostic: /kirakv with a vendor (or gossip) window open - dumps the
-- NPC ID, every item on sale, and any gossip option IDs, so new-season
-- IDs/names can be read straight from chat.
----------------------------------------------------------------------
SLASH_KIRAKV1 = '/kirakv'
SlashCmdList.KIRAKV = function()
	local function p(...) print('|cffff7d0aKiraKV|r', ...) end
	local guid = _G.UnitGUID and _G.UnitGUID('npc')
	if guid and not secret(guid) then
		local unitType, _, _, _, _, id = strsplit('-', guid)
		p('npc:', unitType or '?', '- npcID:', tostring(id))
	else
		p('npc GUID:', guid == nil and 'nil (no vendor open?)' or 'SECRET')
	end
	local n = (_G.GetMerchantNumItems and _G.GetMerchantNumItems()) or 0
	p('merchant items:', n)
	for i = 1, n do
		local nm = MerchantItemName(i)
		if nm then
			local rename, color = Classify(nm)
			p(('  %d: %s%s'):format(i, nm,
				color and (' |cff00ff00-> ' .. tostring(rename) .. '|r')
				or (rename and (' -> ' .. tostring(rename) .. ' (dimmed)') or '')))
		else
			p(('  %d: <name unavailable>'):format(i))
		end
	end
	local CG = _G.C_GossipInfo
	local opts = CG and CG.GetOptions and CG.GetOptions()
	if opts and #opts > 0 then
		p('gossip options:')
		for _, o in ipairs(opts) do
			p(('  id=%s  %s'):format(tostring(o.gossipOptionID), tostring(o.name)))
		end
	end
end
