-- KiraUI-Plugin_MDITeleport.lua
-- MDI teleport gossip: shows each dungeon's teleport ICON in front of the
-- teleport options in the MDI NPC's gossip window.
--
-- Midnight-safe gating: UnitGUID('npc') is restricted (returns nil/secret),
-- so instead of checking the npcID we check the gossipOptionIDs themselves -
-- the window is only rewritten when it contains options we recognize as
-- dungeon teleports. A normal NPC's window is never touched.

local addonName, ns = ...
local _G = _G
-- resolved once: the snapshot below tests every value it prints
local issecretvalue = issecretvalue
local pairs, ipairs, type = pairs, ipairs, type
local format = string.format
local CreateFrame = CreateFrame
local C_Timer = C_Timer

ns.hasMDITeleport = true

----------------------------------------------------------------------
-- Settings
----------------------------------------------------------------------
local DEFAULTS = {
	enable = false,
	iconSize = 25,
	renameGreeting = true,  -- greeting text -> 'Dungeons:'
}

local function GetCfg() return ns.ModuleCfg('mditeleport', DEFAULTS) end
ns.MDIGetCfg = GetCfg

----------------------------------------------------------------------
-- gossipOptionID -> Challenge Mode map ID (MDI PTR, July 2026)
----------------------------------------------------------------------
local OPTION_TO_MAP = {
	[122011] = 584, -- The Blinding Vale
	[136025] = 585, -- Voidscar Arena
	[136028] = 586, -- Den of Nalorakk
	[136037] = 587, -- Murder Row
}

-- Fallback: match the option text against the current season's dungeon
-- names, so new gossipOptionIDs keep working without an addon update.
local function MapFromText(text)
	if type(text) ~= 'string' or text == '' then return nil end
	local CM = _G.C_ChallengeMode
	if not (CM and CM.GetMapTable and CM.GetMapUIInfo) then return nil end
	local maps = CM.GetMapTable()
	if not maps then return nil end
	for _, mapID in ipairs(maps) do
		local name = CM.GetMapUIInfo(mapID)
		if name and name ~= '' and text:find(name, 1, true) then return mapID end
	end
	return nil
end

local function OptionToMap(optionID, text)
	return (optionID and OPTION_TO_MAP[optionID]) or MapFromText(text)
end

----------------------------------------------------------------------
-- Gossip rewrite (same mechanics as the WA: edit the ScrollBox data,
-- then ReinitializeFrames + Rebuild)
----------------------------------------------------------------------
local function DoRewrite()
	local cfg = GetCfg()
	local gf = _G.GossipFrame
	local scrollBox = gf and gf.GreetingPanel and gf.GreetingPanel.ScrollBox
	if not scrollBox then return end
	if not (scrollBox.GetDataProvider and scrollBox:GetDataProvider()) then return end

	-- gate: is this OUR window? require at least one known teleport option,
	-- or two+ options whose text matches season dungeon names
	local known, textHits = 0, 0
	local CG = _G.C_GossipInfo
	local opts = CG and CG.GetOptions and CG.GetOptions()
	if not opts then return end
	for _, o in ipairs(opts) do
		if o.gossipOptionID and OPTION_TO_MAP[o.gossipOptionID] then
			known = known + 1
		elseif MapFromText(o.name) then
			textHits = textHits + 1
		end
	end
	if known == 0 and textHits < 2 then return end

	local CM = _G.C_ChallengeMode
	local sz = cfg.iconSize or 25
	for _, v in scrollBox:EnumerateFrames() do
		local data = v.GetData and v:GetData()
		if data then
			if data.buttonType == 1 and v.GreetingText then
				-- NOTE: no SetHeight here. The scroll layout sizes this row
				-- from GreetingText:GetHeight() only; forcing the frame taller
				-- just draws the text outside its slot -> overlap.
				if cfg.renameGreeting then data.text = 'Dungeons:' end
			elseif data.buttonType == 3 and data.info then
				local mapID = OptionToMap(data.info.gossipOptionID, data.info.name)
				if mapID and CM and CM.GetMapUIInfo then
					local name, _, _, texture = CM.GetMapUIInfo(mapID)
					if name and texture then
						data.info.name = format('|T%d:%d:%d|t %s', texture, sz, sz, name)
					end
				end
			end
		end
	end

	scrollBox:ReinitializeFrames()
	C_Timer.After(0.1, function()
		if scrollBox.Rebuild then scrollBox:Rebuild() end
	end)
end

----------------------------------------------------------------------
-- Events
----------------------------------------------------------------------
-- SNAPSHOT EVERY GOSSIP MENU, so /kiragossip can report one AFTER it closed.
--
-- Reading the options only when you type a command does not work: the menu is
-- usually gone by then, and C_GossipInfo.GetOptions returns nothing. Capturing
-- at GOSSIP_SHOW means the data is already in hand whenever you ask.
--
-- Option NAMES can be secret inside a key, so each is tested and stored as a
-- placeholder rather than risking a raise when printed later. The ID - the part
-- an auto-select whitelist actually needs - stays readable either way.
local lastGossip
local function SnapshotGossip()
	local CG = _G.C_GossipInfo
	if not (CG and CG.GetOptions) then return end
	local okG, opts = pcall(CG.GetOptions)
	if not okG or type(opts) ~= 'table' then return end
	local snap = {}
	for i, o in ipairs(opts) do
		local nm = o.name
		if issecretvalue and issecretvalue(nm) then nm = '<secret>' end
		snap[i] = {
			id    = (issecretvalue and issecretvalue(o.gossipOptionID)) and nil or o.gossipOptionID,
			order = o.orderIndex,
			name  = (type(nm) == 'string') and nm or '?',
		}
	end
	local okI, _, _, _, _, _, _, _, instID = pcall(_G.GetInstanceInfo)
	lastGossip = {
		options = snap,
		instance = (okI and not (issecretvalue and issecretvalue(instID))) and instID or nil,
		when = _G.GetTime and _G.GetTime() or 0,
	}
end

local evt = CreateFrame('Frame')
evt:RegisterEvent('GOSSIP_SHOW')
evt:SetScript('OnEvent', function()
	-- snapshot ALWAYS, even with the MDI rewrite disabled: this is a diagnostic
	-- and gating it behind an unrelated setting is how it comes up empty
	pcall(SnapshotGossip)
	local cfg = GetCfg()
	if not cfg.enable then return end
	-- pcall: if Blizzard changes the gossip frame internals, fail silently
	-- instead of erroring in the middle of an MDI run
	pcall(DoRewrite)
end)

_G.SLASH_KIRAGOSSIP1 = '/kiragossip'
_G.SlashCmdList.KIRAGOSSIP = function()
	local function p(...) print('|cffff7d0aKiraGossip|r', ...) end
	if not lastGossip then
		p('no gossip menu seen yet - talk to the NPC first, then run this.')
		return
	end
	p(('last menu: %d option(s)%s'):format(#lastGossip.options,
		lastGossip.instance and (' | instanceID ' .. lastGossip.instance) or ''))
	if #lastGossip.options == 0 then
		p('the menu had no selectable options (quest-only or a plain text window).')
	end
	for i, o in ipairs(lastGossip.options) do
		p(('  %d. gossipOptionID = |cffffd200%s|r  (orderIndex %s)  %s'):format(
			i, tostring(o.id or 'UNREADABLE'), tostring(o.order or '?'), o.name))
	end
	p('Send Kira/Claude the gossipOptionID line for the option you want auto-selected.')
end
