
-- ============================================================
-- FILE: KeyDiagnostics.lua
-- PURPOSE:
--   Auto-start/auto-stop diagnostic probe that captures rich
--   context for known Mythic+-specific issues. Enables on
--   CHALLENGE_MODE_START, disables on CHALLENGE_MODE_COMPLETED.
--   All output routes through MidnightUI_DebugSource("KeyDiag").
-- WHAT IT WATCHES:
--   1. ADDON_ACTION_FORBIDDEN / ADDON_ACTION_BLOCKED — on fire,
--      runs issecurevariable() against likely-tainted WorldMap
--      attributes to name the specific taint source.
--   2. Chat event arrivals — increments a counter for every
--      CHAT_MSG_* event so we can compare against Messenger's
--      stored count post-key (detects silent event drops).
--   3. WorldMap open/close transitions — polled, no hooks, so
--      the probe adds no taint surface.
--   4. CHALLENGE_MODE_RESET — logs mid-key abandons.
-- NOT A RUNTIME FEATURE — pure diagnostics. Safe to remove
-- entirely once the WorldMap taint source is identified.
-- ============================================================

local function Log(msg)
    if _G.MidnightUI_DebugSource then
        _G.MidnightUI_DebugSource("KeyDiag", tostring(msg))
    end
end

local state = {
    active = false,
    chatEventCount = 0,
    forbiddenCount = 0,
    mapShown = false,
    pollTicker = nil,
    sessionStart = 0,
}

local TRACKED_MAP_PROPS = {
    "SetShown", "Show", "Hide", "SetAlpha", "SetScale", "SetSize",
    "SetPoint", "ClearAllPoints", "SetParent", "SetFrameStrata",
    "SetFrameLevel", "SetMovable", "SetUserPlaced", "SetScript",
    "SetAttribute", "HookScript", "RegisterEvent",
}

local function DumpTaintReport(action, addon)
    Log(string.format("FORBIDDEN #%d action=%s addon=%s --- taint report follows ---",
        state.forbiddenCount, tostring(action), tostring(addon)))

    local map = _G.WorldMapFrame
    if not map then
        Log("  WorldMapFrame not present")
    else
        for _, prop in ipairs(TRACKED_MAP_PROPS) do
            local okCall, isSecure, source = pcall(issecurevariable, map, prop)
            if okCall then
                if isSecure == false then
                    Log(string.format("  TAINTED WorldMapFrame.%s by %s", prop, tostring(source or "?")))
                end
            end
        end
    end

    local globals = {
        "ShowUIPanel", "HideUIPanel", "SetUIPanel", "UpdateUIPanelPositions",
        "ToggleWorldMap", "OpenWorldMap", "SetMapID",
    }
    for _, g in ipairs(globals) do
        local okCall, isSecure, source = pcall(issecurevariable, g)
        if okCall and isSecure == false then
            Log(string.format("  TAINTED global %s by %s", g, tostring(source or "?")))
        end
    end

    local stack = ""
    if type(debugstack) == "function" then
        local okStack, ds = pcall(debugstack, 3, 8, 8)
        if okStack and ds then stack = ds end
    end
    if stack ~= "" then
        Log("  stack: " .. stack:gsub("\n", " | "))
    end
end

local function PollMapState()
    if not state.active then return end
    local map = _G.WorldMapFrame
    local isShown = map and map.IsShown and map:IsShown() or false
    if isShown ~= state.mapShown then
        state.mapShown = isShown
        Log(string.format("WorldMap %s (forbiddenSoFar=%d chatSoFar=%d)",
            isShown and "OPENED" or "CLOSED", state.forbiddenCount, state.chatEventCount))
    end
end

local function StartProbe()
    if state.active then return end
    state.active = true
    state.chatEventCount = 0
    state.forbiddenCount = 0
    state.mapShown = false
    state.sessionStart = time()


    if C_Timer and C_Timer.NewTicker then
        state.pollTicker = C_Timer.NewTicker(0.25, PollMapState)
    end
end

local function StopProbe()
    if not state.active then return end
    state.active = false

    if state.pollTicker and state.pollTicker.Cancel then
        state.pollTicker:Cancel()
        state.pollTicker = nil
    end

    local stored = 0
    if _G.MessengerDB and _G.MessengerDB.History and _G.MessengerDB.History["Group"] then
        local messages = _G.MessengerDB.History["Group"].messages or {}
        for _, m in ipairs(messages) do
            if m and m.epoch and m.epoch >= state.sessionStart then
                stored = stored + 1
            end
        end
    end

    Log(string.format("==== KEY DIAGNOSTIC SUMMARY: chatEvents=%d messengerStored=%d forbidden=%d duration=%ds ====",
        state.chatEventCount, stored, state.forbiddenCount, time() - state.sessionStart))
end

local CHAT_EVENTS = {
    "CHAT_MSG_SAY", "CHAT_MSG_YELL", "CHAT_MSG_PARTY", "CHAT_MSG_PARTY_LEADER",
    "CHAT_MSG_RAID", "CHAT_MSG_RAID_LEADER",
    "CHAT_MSG_INSTANCE_CHAT", "CHAT_MSG_INSTANCE_CHAT_LEADER",
    "CHAT_MSG_WHISPER", "CHAT_MSG_WHISPER_INFORM",
}

local probeFrame = CreateFrame("Frame")
probeFrame:RegisterEvent("CHALLENGE_MODE_START")
probeFrame:RegisterEvent("CHALLENGE_MODE_COMPLETED")
probeFrame:RegisterEvent("CHALLENGE_MODE_RESET")
probeFrame:RegisterEvent("ADDON_ACTION_FORBIDDEN")
probeFrame:RegisterEvent("ADDON_ACTION_BLOCKED")
probeFrame:RegisterEvent("PLAYER_LOGIN")
for _, e in ipairs(CHAT_EVENTS) do
    probeFrame:RegisterEvent(e)
end

probeFrame:SetScript("OnEvent", function(self, event, ...)
    local arg1, arg2 = ...
    pcall(function()
        if event == "PLAYER_LOGIN" then
            local activeMap
            if C_ChallengeMode and C_ChallengeMode.GetActiveChallengeMapID then
                activeMap = C_ChallengeMode.GetActiveChallengeMapID()
            end
            if activeMap then
                StartProbe()
            end
            return
        end

        if event == "CHALLENGE_MODE_START" then
            StartProbe()
            return
        end

        if event == "CHALLENGE_MODE_COMPLETED" or event == "CHALLENGE_MODE_RESET" then
            if event == "CHALLENGE_MODE_RESET" then
                Log("CHALLENGE_MODE_RESET fired — key abandoned or reset")
            end
            StopProbe()
            return
        end

        if not state.active then return end

        if event == "ADDON_ACTION_FORBIDDEN" or event == "ADDON_ACTION_BLOCKED" then
            if arg1 == "MidnightUI" then
                state.forbiddenCount = state.forbiddenCount + 1
                DumpTaintReport(arg2, arg1)
            end
            return
        end

        for _, e in ipairs(CHAT_EVENTS) do
            if event == e then
                state.chatEventCount = state.chatEventCount + 1
                return
            end
        end
    end)
end)
