local _, ns = ...
local B, C, L, DB = unpack(ns)

--[[
	OrzUI DevTools:
	/rl, reload ui
	/nt, get gametooltip names
	/nf, get frame names
	/ns, get spell name and description
	/ng, show grid on WorldFrame
	/getid, get instance id
	/getnpc, get npc name and id
	/getenc, get selected encounters info
]]

local strfind, format, strsplit = string.find, string.format, string.split
local gsub, pairs, tonumber, tostring = gsub, pairs, tonumber, tostring
local floor, ceil = math.floor, math.ceil
local IsQuestFlaggedCompleted = C_QuestLog.IsQuestFlaggedCompleted
local GetSpellDescription = C_Spell.GetSpellDescription
local GetSpellName = C_Spell.GetSpellName

DB.Devs = {
	["烂柯人-时光之穴"] = true,
	["Yichen-贫瘠之地"] = true,
	["Helements-贫瘠之地"] = true,
	["Lightcasting-时光之穴"] = true,
	["Earthenss-时光之穴"] = true,
	["柒维胡里-烈焰峰"] = true,
	["桐敷沙子-血吼"] = true,
	["心中只有邪污-金色平原"] = true,
	["星氪-烈焰峰"] = true,
	["曾經滄海-主宰之剑"] = true,
	["璐米娜丝-金色平原"] = true,
	["乌鸦凿飞机-凤凰之神"] = true,
}
DB.DevsID = {
	["ShiGuang#51562"] = true,
	["河河#5769"] = true,
	["贝戎#52863"] = true,
	["zongbao#5756"] = true,
	["柒维狐狸#5450"] = true,
	["WooSphinx#5211"] = true,
	["柒春#5607"] = true,
}
local function isDeveloper()
	local rawName = gsub(DB.MyFullName, "%s", "")
	return DB.Devs[rawName]
end
local function isFriends()
	local bnetID = select(2, BNGetInfo())
	return DB.DevsID[bnetID]
end
if isFriends() then print("|cff70C0F5尊贵的2 UI VVVIP，感谢你对插件的贡献和帮助！") end
DB.isDeveloper = isDeveloper()

SlashCmdList["ORZUI_ENUMTIP"] = function()
	local enumf = EnumerateFrames()
	while enumf do
		if (enumf:IsObjectType("GameTooltip") or strfind((enumf:GetName() or ""):lower(), "tip")) and enumf:IsVisible() and enumf:GetPoint() then
			print(enumf:GetName())
		end
		enumf = EnumerateFrames(enumf)
	end
end
SLASH_ORZUI_ENUMTIP1 = "/nt"

SlashCmdList["ORZUI_ENUMFRAME"] = function()
	local frame = EnumerateFrames()
	while frame do
		if (frame:IsVisible() and MouseIsOver(frame)) then
			print(frame:GetName() or format(UNKNOWN..": [%s]", tostring(frame)))
		end
		frame = EnumerateFrames(frame)
	end
end
SLASH_ORZUI_ENUMFRAME1 = "/nf"

SlashCmdList["ORZUI_DUMPSPELL"] = function(arg)
	local name = GetSpellName(arg)
	if not name then return end
	local des = GetSpellDescription(arg)
	print("|cff70C0F5------------------------")
	print(" \124T"..C_Spell.GetSpellTexture(arg)..":16:16:::64:64:5:59:5:59\124t", DB.InfoColor..arg)
	print(NAME, DB.InfoColor..(name or "nil"))
	print(DESCRIPTION, DB.InfoColor..(des or "nil"))
	print("|cff70C0F5------------------------")
end
SLASH_ORZUI_DUMPSPELL1 = "/ns"

SlashCmdList["INSTANCEID"] = function()
	local name, _, _, _, _, _, _, id = GetInstanceInfo()
	print(name, id)
end
SLASH_INSTANCEID1 = "/getid"

SlashCmdList["ORZUI_NPCID"] = function()
	local name = UnitName("target")
	local guid = UnitGUID("target")
	if name and guid then
		local npcID = B.GetNPCID(guid)
		print(name, DB.InfoColor..(npcID or "nil"))
	end
end
SLASH_ORZUI_NPCID1 = "/getnpc"

SlashCmdList["ORZUI_GETFONT"] = function(msg)
	local font = _G[msg]
	if not font then print(msg, "not found.") return end
	local a, b, c = font:GetFont()
	print(msg,a,b,c)
end
SLASH_ORZUI_GETFONT1 = "/nff"

SlashCmdList["ORZUI_CHECK_QUEST"] = function(msg)
	if not msg then return end
	print("QuestID "..msg.." complete:", IsQuestFlaggedCompleted(tonumber(msg)))
end
SLASH_ORZUI_CHECK_QUEST1 = "/ncq"


SlashCmdList["ORZUI_GET_INSTANCES"] = function()
	if not EncounterJournal then return end
	local tierID = EJ_GetCurrentTier()
	print("local _, ns = ...")
	print("local B, C, L, DB = unpack(ns)")
	print("local module = B:GetModule(\"AurasTable\")")
	print("local TIER = "..tierID)
	print("local INSTANCE")
	local i = 0
	while true do
		i = i + 1
		local instID, instName = EJ_GetInstanceByIndex(i, false)
		if not instID then return end
		print("INSTANCE = "..instID.." -- "..instName)
	end
end
SLASH_ORZUI_GET_INSTANCES1 = "/getinst"

SlashCmdList["ORZUI_GET_ENCOUNTERS"] = function()
	if not EncounterJournal then return end
	local tierID = EJ_GetCurrentTier()
	local instID = EncounterJournal.instanceID
	EJ_SelectInstance(instID)
	local instName = EJ_GetInstanceInfo()
	print("local _, ns = ...")
	print("local B, C, L, DB = unpack(ns)")
	print("local module = B:GetModule(\"AurasTable\")")
	print("local TIER = "..tierID)
	print("local INSTANCE = "..instID.." -- "..instName)
	print("local BOSS")
	local i = 0
	while true do
		i = i + 1
		local name, _, boss = EJ_GetEncounterInfoByIndex(i)
		if not name then return end
		print("BOSS = "..boss.." -- "..name)
	end
end
SLASH_ORZUI_GET_ENCOUNTERS1 = "/getenc"

SlashCmdList["ORZUI_DUMPSPELLS"] = function(arg)
	for spell in gmatch(arg, "%d+") do
		local name = GetSpellName(spell)
		if name then
			print("module:RegisterDebuff(TIER, INSTANCE, BOSS, "..spell..") -- "..name)
		end
	end
end
SLASH_ORZUI_DUMPSPELLS1 = "/getss"

SlashCmdList["ORZUI_GET_TIERSETS"] = function()
	if not EncounterJournal then return end
	local frame = EncounterJournal.LootJournalItems.ItemSetsFrame
	local classFilter = frame:GetClassAndSpecFilters()
	local classInfo = C_CreatureInfo.GetClassInfo(classFilter)
	local sets = frame.itemSets
	local setID = sets and sets[1].setID
	if not setID then return end
	local data = C_LootJournal.GetItemSetItems(setID)
	local text = ""
	for i = 1, 5 do
		local d = data[i]
		text = "["..d.itemID.."] = true, "..text
	end
	print("--", classInfo.classFile)
	print(text)
end
SLASH_ORZUI_GET_TIERSETS1 = "/getts"

-- Grids
local grid
local boxSize = 32
local function Grid_Create()
	grid = CreateFrame("Frame", nil, UIParent)
	grid.boxSize = boxSize
	grid:SetAllPoints(UIParent)

	local size = 2
	local width = GetScreenWidth()
	local ratio = width / GetScreenHeight()
	local height = GetScreenHeight() * ratio

	local wStep = width / boxSize
	local hStep = height / boxSize

	for i = 0, boxSize do
		local tx = grid:CreateTexture(nil, "BACKGROUND")
		if i == boxSize / 2 then
			tx:SetColorTexture(1, 0, 0, .5)
		else
			tx:SetColorTexture(0, 0, 0, .5)
		end
		tx:SetPoint("TOPLEFT", grid, "TOPLEFT", i*wStep - (size/2), 0)
		tx:SetPoint("BOTTOMRIGHT", grid, "BOTTOMLEFT", i*wStep + (size/2), 0)
	end
	height = GetScreenHeight()

	do
		local tx = grid:CreateTexture(nil, "BACKGROUND")
		tx:SetColorTexture(1, 0, 0, .5)
		tx:SetPoint("TOPLEFT", grid, "TOPLEFT", 0, -(height/2) + (size/2))
		tx:SetPoint("BOTTOMRIGHT", grid, "TOPRIGHT", 0, -(height/2 + size/2))
	end

	for i = 1, floor((height/2)/hStep) do
		local tx = grid:CreateTexture(nil, "BACKGROUND")
		tx:SetColorTexture(0, 0, 0, .5)

		tx:SetPoint("TOPLEFT", grid, "TOPLEFT", 0, -(height/2+i*hStep) + (size/2))
		tx:SetPoint("BOTTOMRIGHT", grid, "TOPRIGHT", 0, -(height/2+i*hStep + size/2))

		tx = grid:CreateTexture(nil, "BACKGROUND")
		tx:SetColorTexture(0, 0, 0, .5)

		tx:SetPoint("TOPLEFT", grid, "TOPLEFT", 0, -(height/2-i*hStep) + (size/2))
		tx:SetPoint("BOTTOMRIGHT", grid, "TOPRIGHT", 0, -(height/2-i*hStep + size/2))
	end
end

local function Grid_Show()
	if not grid then
		Grid_Create()
	elseif grid.boxSize ~= boxSize then
		grid:Hide()
		Grid_Create()
	else
		grid:Show()
	end
end

local isAligning = false
SlashCmdList["TOGGLEGRID"] = function(arg)
	if isAligning or arg == "1" then
		if grid then grid:Hide() end
		isAligning = false
	else
		boxSize = (ceil((tonumber(arg) or boxSize) / 32) * 32)
		if boxSize > 256 then boxSize = 256 end
		Grid_Show()
		isAligning = true
	end
end
SLASH_TOGGLEGRID1 = "/ng"

-- ================================================================
-- Frame Inspector - 鼠标指向查看元素所属插件
-- 参考实现: WTFisThisAddon
-- ================================================================
local FI = {
	active = false,
	scanThrottle = 0.05,
	parentDepth = 6,
	ownFrames = {},
	addonCache = {},
	frameInfoCache = setmetatable({}, { __mode = "k" }),
	frameRootCache = setmetatable({}, { __mode = "k" }),
}

-- 安全调用封装
local function SafeCall(fn, ...)
	local ok, result = pcall(fn, ...)
	return ok and result or nil
end

-- 安全获取数字
local function SafeNum(fn, obj)
	local ok, v = pcall(fn, obj)
	if not ok or v == nil then return 0 end
	local ok2, n = pcall(math.floor, v)
	return ok2 and n or 0
end

-- 路径标准化
local function NormalizePath(location)
	if not location then return nil end
	return location:gsub("^@", ""):gsub("/", "\\")
end

-- 构建插件缓存
local function BuildAddonCache()
	local getCount = (C_AddOns and C_AddOns.GetNumAddOns) or GetNumAddOns
	local getInfo = (C_AddOns and C_AddOns.GetAddOnInfo) or GetAddOnInfo
	if not getCount or not getInfo then return end

	local count = getCount()
	for i = 1, count do
		local folder, title = getInfo(i)
		if folder then
			FI.addonCache[folder:lower()] = {
				folder = folder,
				title = (title and title ~= "") and title or folder,
			}
		end
	end
end

-- 获取插件标题
local function GetAddonTitle(folderName)
	if not folderName then return nil end
	local entry = FI.addonCache[folderName:lower()]
	return entry and entry.title or folderName
end

-- 解析来源位置
local function ParseLocation(location)
	if not location or location == "" or location == "UnknownFile" then
		return nil
	end
	local loc = NormalizePath(location)

	-- AddOns 路径
	local addonFolder, file, line =
		loc:match("[Ii]nterface\\[Aa]dd[Oo]ns\\([^\\]+)\\(.+):(%d+)")
	if addonFolder then
		-- Blizzard_ 前缀 = 暴雪自带模块
		if addonFolder:match("^[Bb]lizzard_") then
			return { type = "blizzard", name = addonFolder, title = DEFAULTS,
					 file = file, line = line, raw = location }
		end
		return { type = "addon", name = addonFolder, title = GetAddonTitle(addonFolder),
				 file = file, line = line, raw = location }
	end

	-- Blizzard 默认UI
	if loc:find("[Ff]rame[Xx][Mm][Ll]")
	or loc:find("[Bb]lizzard[Ii]nterface[Cc]ode")
	or loc:find("[Ii]nterface\\[Bb]uilt[Ii]n") then
		return { type = "blizzard", name = "Blizzard UI", title = DEFAULTS,
				 file = loc:match("([^\\]+%.lua):%d+") or "?",
				 line = loc:match(":(%d+)$") or "?",
				 raw = location }
	end

	return { type = "unknown", name = "Unknown", title = UNKNOWN,
			 file = loc, line = "?", raw = location }
end

-- 快速获取文件夹名
local function QuickGetFolder(location)
	if not location then return nil end
	return NormalizePath(location):match("[Ii]nterface\\[Aa]dd[Oo]ns\\([^\\]+)\\")
end

-- 从框架名猜测插件
local function GuessAddonFromName(frameName)
	if not frameName then return nil end
	return frameName:match("^([A-Za-z][A-Za-z0-9]+)[_%-]") or nil
end

-- 分析框架
local function AnalyzeFrame(frame)
	if FI.frameInfoCache[frame] then return FI.frameInfoCache[frame] end

	local result = {
		frameName = frame:GetName() or "(unnamed)",
		objectType = SafeCall(frame.GetObjectType, frame) or "Frame",
		debugName = SafeCall(frame.GetDebugName, frame) or "(none)",
		source = nil,
		nameGuess = nil,
		rawLoc = nil,
		sourceLine = nil,
		anchorInfo = nil,
		scripts = {},
		width = SafeNum(frame.GetWidth, frame),
		height = SafeNum(frame.GetHeight, frame),
		strata = SafeCall(frame.GetFrameStrata, frame) or "?",
		level = SafeCall(frame.GetFrameLevel, frame) or "?",
		isShown = SafeCall(frame.IsShown, frame),
		isVisible = SafeCall(frame.IsVisible, frame),
		numChildren = SafeNum(frame.GetNumChildren, frame),
		numRegions = SafeNum(frame.GetNumRegions, frame),
		parentName = nil,
		parentChain = {},
		contributors = {},
	}

	local parent = frame:GetParent()
	if parent then
		result.parentName = parent:GetName() or format("(unnamed %s)", SafeCall(parent.GetObjectType, parent) or "Frame")
	end

	local loc = SafeCall(frame.GetSourceLocation, frame)
	if loc then
		result.source = ParseLocation(loc)
		result.rawLoc = loc
		if result.source and result.source.line then
			result.sourceLine = result.source.file .. ":" .. result.source.line
		end
	end

	local ok, point, relFrame, relPoint, x, y = pcall(frame.GetPoint, frame, 1)
	if ok and point then
		local relName = "?"
		if type(relFrame) == "table" and relFrame.GetName then
			relName = relFrame:GetName() or format("(%s)", SafeCall(relFrame.GetObjectType, relFrame) or "Frame")
		elseif type(relFrame) == "string" then
			relName = relFrame
		end
		x = tonumber(x) or 0
		y = tonumber(y) or 0
		result.anchorInfo = format("%s, %s, %s, %d, %d", point, relName, relPoint or "?", x, y)
	end

	local scriptList = {
		"OnClick", "OnEnter", "OnLeave", "OnShow", "OnHide",
		"OnUpdate", "OnEvent", "OnMouseDown", "OnMouseUp",
		"OnDragStart", "OnDragStop", "OnReceiveDrag",
		"OnValueChanged", "OnMinMaxChanged", "OnEnterPressed",
		"OnTextChanged", "OnColorSelect", "OnCursorChanged",
	}
	for _, script in ipairs(scriptList) do
		local ok, handler = pcall(frame.GetScript, frame, script)
		if ok and handler then
			result.scripts[#result.scripts+1] = script
		end
	end

	if not result.source or result.source.type == "unknown" then
		result.nameGuess = GuessAddonFromName(frame:GetName())
	end

	local seen = {}
	local mainFolder = result.source and result.source.name or ""
	if mainFolder ~= "" then seen[mainFolder:lower()] = true end

	local cur = frame:GetParent()
	local depth = 0
	while cur and cur ~= UIParent and cur ~= WorldFrame and depth < FI.parentDepth do
		local loc2 = SafeCall(cur.GetSourceLocation, cur)
		local src2 = loc2 and ParseLocation(loc2) or nil

		tinsert(result.parentChain, {
			name = cur:GetName() or format("(unnamed #%d)", depth),
			source = src2,
		})

		if src2 and src2.type == "addon" and not seen[src2.name:lower()] then
			tinsert(result.contributors, src2.title or src2.name)
			seen[src2.name:lower()] = true
		end

		cur = cur:GetParent()
		depth = depth + 1
	end

	FI.frameInfoCache[frame] = result
	return result
end

-- 查找同插件根框架
local function FindAddonRoot(frame, addonFolder)
	if not addonFolder then return frame end
	if FI.frameRootCache[frame] then return FI.frameRootCache[frame] end

	local root = frame
	local cur = frame:GetParent()
	while cur and cur ~= UIParent and cur ~= WorldFrame do
		local loc2 = SafeCall(cur.GetSourceLocation, cur)
		local folder = loc2 and QuickGetFolder(loc2) or nil
		if folder and folder:lower() == addonFolder:lower() then
			root = cur
		else
			break
		end
		cur = cur:GetParent()
	end

	FI.frameRootCache[frame] = root
	return root
end

-- 注册自有框架（排除检测）
local function RegisterOwnFrame(frame)
	FI.ownFrames[frame] = true
end

local function IsOwnFrame(frame)
	local cur = frame
	while cur do
		if FI.ownFrames[cur] then return true end
		cur = cur.GetParent and cur:GetParent() or nil
	end
	return false
end

-- 高亮框
local hlFrame = CreateFrame("Frame", "OrzUI_FrameInspector_HL", UIParent)
hlFrame:SetFrameStrata("TOOLTIP")
hlFrame:SetFrameLevel(100)
hlFrame:EnableMouse(false)
hlFrame:Hide()
RegisterOwnFrame(hlFrame)

local hlBg = hlFrame:CreateTexture(nil, "BACKGROUND")
hlBg:SetAllPoints()
hlBg:SetColorTexture(0, 0.8, 1, 0.10)

local function MakeBorderLine(parent)
	local t = parent:CreateTexture(nil, "BORDER")
	t:SetColorTexture(0, 1, 1, 1)
	return t
end
local hlTop = MakeBorderLine(hlFrame)
local hlBottom = MakeBorderLine(hlFrame)
local hlLeft = MakeBorderLine(hlFrame)
local hlRight = MakeBorderLine(hlFrame)

hlTop:SetHeight(2); hlTop:SetPoint("TOPLEFT", hlFrame, "TOPLEFT"); hlTop:SetPoint("TOPRIGHT", hlFrame, "TOPRIGHT")
hlBottom:SetHeight(2); hlBottom:SetPoint("BOTTOMLEFT", hlFrame, "BOTTOMLEFT"); hlBottom:SetPoint("BOTTOMRIGHT", hlFrame, "BOTTOMRIGHT")
hlLeft:SetWidth(2); hlLeft:SetPoint("TOPLEFT", hlFrame, "TOPLEFT"); hlLeft:SetPoint("BOTTOMLEFT", hlFrame, "BOTTOMLEFT")
hlRight:SetWidth(2); hlRight:SetPoint("TOPRIGHT", hlFrame, "TOPRIGHT"); hlRight:SetPoint("BOTTOMRIGHT", hlFrame, "BOTTOMRIGHT")

-- 信息弹出框（使用系统原生 GameTooltip）
local popup = GameTooltip

local function RefreshPopup(info)
	popup:ClearLines()
	popup:SetOwner(UIParent, "ANCHOR_NONE")

	local src = info.source

	if src then
		if src.type == "blizzard" then
			popup:AddDoubleLine(DEFAULTS, src.name or "", 0.4, 0.6, 1, 0.5, 0.5, 0.5)
		elseif src.type == "addon" then
			popup:AddLine(src.title or src.name, 1, 1, 0.2)
			if src.file and src.file ~= "?" then
				popup:AddDoubleLine(L["File"], src.file, 0.5, 0.5, 0.5, 0.7, 0.7, 0.7)
			end
		else
			if info.nameGuess then
				popup:AddDoubleLine(info.nameGuess, L["Guessed"], 0.8, 0.65, 0, 0.5, 0.5, 0.5)
			else
				popup:AddLine(UNKNOWN, 0.5, 0.5, 0.5)
			end
		end
	else
		popup:AddDoubleLine(L["No Source Info"], "SourceLocation", 0.5, 0.5, 0.5, 0.35, 0.35, 0.35)
	end

	popup:AddLine(" ")
	popup:AddDoubleLine(info.objectType, info.frameName, 0.6, 0.8, 1, 0.8, 0.8, 0.8)

	if info.parentName then
		popup:AddDoubleLine(L["Parent"], info.parentName, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6)
	end

	if #info.contributors > 0 then
		popup:AddLine(" ")
		popup:AddLine(L["Affecting Addons"], 1, 0.6, 0)
		for _, name in ipairs(info.contributors) do
			popup:AddLine("  - " .. name, 0.75, 0.7, 0.55)
		end
	end

	popup:AddLine(" ")
	popup:AddDoubleLine(L["Size"], info.width .. " x " .. info.height, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6)
	popup:AddDoubleLine(L["Layer"], (info.strata or "?") .. " / " .. (info.level or "?"), 0.5, 0.5, 0.5, 0.6, 0.6, 0.6)

	local stateParts = {}
	if info.isShown then stateParts[#stateParts+1] = "|cff00ff00Shown|r" else stateParts[#stateParts+1] = "|cff666666Hidden|r" end
	if info.isVisible then stateParts[#stateParts+1] = "|cff00ff00Visible|r" else stateParts[#stateParts+1] = "|cff666666NotVisible|r" end
	popup:AddDoubleLine(L["State"], table.concat(stateParts, "  "), 0.5, 0.5, 0.5, 1, 1, 1)

	if info.sourceLine then
		popup:AddDoubleLine(L["Source Line"], info.sourceLine, 0.5, 0.5, 0.5, 0.7, 0.7, 0.7)
	end

	if info.anchorInfo then
		popup:AddDoubleLine(L["Anchor"], info.anchorInfo, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6)
	end

	if info.scripts and #info.scripts > 0 then
		popup:AddDoubleLine(L["Scripts"], table.concat(info.scripts, ", "), 0.5, 0.5, 0.5, 0.6, 0.8, 1)
	end

	popup:AddDoubleLine(L["Children"], info.numChildren .. " / " .. info.numRegions, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6)

	popup:AddLine(" ")
	popup:AddLine("|cff00ccff/fi|r |cff888888- " .. L["FrameInspector Disable Hint"] .. "|r", 0.4, 0.4, 0.4)

	popup:Show()
end

local function PositionPopup()
	local cx, cy = GetCursorPosition()
	local s = UIParent:GetEffectiveScale()
	cx, cy = cx / s, cy / s

	local pw = popup:GetWidth()
	local ph = popup:GetHeight()
	local sw = GetScreenWidth()
	local sh = GetScreenHeight()

	local px = cx + 22
	local py = cy + 14
	if px + pw > sw then px = cx - pw - 10 end
	if py + ph > sh then py = cy - ph - 10 end
	if py < 4 then py = 4 end

	popup:ClearAllPoints()
	popup:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", px, py)
end

-- 扫描循环
local scanFrame = CreateFrame("Frame")
local prevFocus = nil
local timeSince = 0
scanFrame:Hide()

scanFrame:SetScript("OnUpdate", function(self, elapsed)
	if popup:IsShown() then PositionPopup() end

	timeSince = timeSince + elapsed
	if timeSince < FI.scanThrottle then return end
	timeSince = 0

	local foci = GetMouseFoci and GetMouseFoci() or { GetMouseFocus and GetMouseFocus() }
	local focus = foci and foci[1]

	if not focus or focus == WorldFrame or focus == UIParent or IsOwnFrame(focus) then
		if prevFocus ~= nil then
			hlFrame:Hide()
			popup:Hide()
			prevFocus = nil
		end
		return
	end

	if focus == prevFocus then return end
	prevFocus = focus

	local info = AnalyzeFrame(focus)
	local addonFolder = (info.source and info.source.type == "addon") and info.source.name or nil
	local rootFrame = addonFolder and FindAddonRoot(focus, addonFolder) or focus

	hlFrame:ClearAllPoints()
	hlFrame:SetAllPoints(rootFrame)
	hlFrame:Show()

	RefreshPopup(info)
	popup:Show()
end)

-- 切换功能
function B:ToggleFrameInspector(forceState)
	if forceState ~= nil then
		FI.active = forceState
	else
		FI.active = not FI.active
	end

	if C.db and C.db.Misc then
		C.db.Misc.FrameInspector = FI.active
	end

	if FI.active then
		if GetCVar and GetCVar("enableSourceLocationLookup") ~= "1" then
			SetCVar("enableSourceLocationLookup", "1")
			print(DB.OrzUIString .. " " .. L["FrameInspector"] .. ": " .. L["SourceLocation enabled, please /reload"])
			FI.active = false
			if C.db and C.db.Misc then
				C.db.Misc.FrameInspector = false
			end
			return
		end

		if not next(FI.addonCache) then
			BuildAddonCache()
		end

		scanFrame:Show()
		print(DB.OrzUIString .. " " .. L["FrameInspector"] .. ": " .. L["Enabled"])
	else
		scanFrame:Hide()
		hlFrame:Hide()
		popup:Hide()
		prevFocus = nil
		print(DB.OrzUIString .. " " .. L["FrameInspector"] .. ": " .. L["Disabled"])
	end
end

-- 按键绑定调用
_G.ToggleOrzUIFrameInspector = function()
	B:ToggleFrameInspector()
end

-- 命令行
SlashCmdList["ORZUI_FRAMEINSPECTOR"] = function()
	B:ToggleFrameInspector()
end
SLASH_ORZUI_FRAMEINSPECTOR1 = "/fi"
SLASH_ORZUI_FRAMEINSPECTOR2 = "/frameinspector"

BINDING_NAME_ORZUI_FRAMEINSPECTOR = L["FrameInspector Keybind"]

-- 登录时根据设置自动启动
B:RegisterEvent("PLAYER_LOGIN", function()
	if C.db and C.db.Misc and C.db.Misc.FrameInspector then
		C_Timer.After(1, function()
			B:ToggleFrameInspector(true)
		end)
	end
end)