
-- ApiVault Must-Have Developer Tools Panel. Rebuilt in v1.2.0 DevTools stability pass.
-- UI-only. No runtime source reads and no protected/secure execution.
local addonName, ApiVault = ...

ApiVault.DeveloperMustHaveToolsPanel = ApiVault.DeveloperMustHaveToolsPanel or {}
local Panel = ApiVault.DeveloperMustHaveToolsPanel

local TOOLS = {
	{ key = "addon", label = "Addon Lookup" },
	{ key = "errors", label = "Error Inbox" },
	{ key = "sv", label = "SavedVariables" },
	{ key = "taint", label = "Taint / Combat" },
	{ key = "package", label = "Package" },
	{ key = "performance", label = "Performance" },
	{ key = "compat", label = "API Matrix" },
	{ key = "snippets", label = "Snippets" },
	{ key = "export", label = "Session Export" },
}

local TOOL_BY_KEY = {}
for _, tool in ipairs(TOOLS) do TOOL_BY_KEY[tool.key] = tool end

local function createBackdropFrame(name, parent)
	return CreateFrame("Frame", name, parent, BackdropTemplateMixin and "BackdropTemplate" or nil)
end

local function setBackdrop(frame, r, g, b, a)
	if not frame.SetBackdrop then return end
	frame:SetBackdrop({
		bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
		edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
		tile = true, tileSize = 32, edgeSize = 32,
		insets = { left = 8, right = 8, top = 8, bottom = 8 },
	})
	frame:SetBackdropColor(r or 0.04, g or 0.04, b or 0.06, a or 0.96)
end

local function createText(parent, template)
	local fs = parent:CreateFontString(nil, "OVERLAY", template or "GameFontNormal")
	fs:SetJustifyH("LEFT")
	return fs
end

local function trim(value)
	value = tostring(value or "")
	return (value:gsub("^%s+", ""):gsub("%s+$", ""))
end

local function firstLine(value)
	value = tostring(value or "")
	return trim((string.match(value, "([^\r\n]+)") or value))
end

local function looksLikeError(text)
	text = tostring(text or "")
	local lower = string.lower(text)
	return string.find(lower, "attempt to", 1, true) or string.find(lower, "bad argument", 1, true) or string.find(text, "%.lua:%d+")
end

function Panel:GetTool(key)
	return TOOL_BY_KEY[key or self.mode or "addon"] or TOOL_BY_KEY.addon
end

function Panel:GetHelp(mode)
	mode = mode or self.mode or "addon"
	local help = {
		addon = "Type an addon folder/name such as StockVault, then click Run. Shows runtime addon metadata, Public API registration, Error Inbox matches and Bug Advisor matches.",
		errors = "Paste an error stack or type a filter. If you paste an error, ApiVault ingests it and shows an explanation.",
		sv = "Type an addon filter such as StockVault, or leave blank to scan runtime SavedVariables declarations visible to the client.",
		taint = "Paste Lua code or an error stack. ApiVault scans for secure/taint/combat-sensitive patterns and generates OOC/combat test steps.",
		package = "Paste .toc text. ApiVault validates required metadata, file references and release-readiness checklist items.",
		performance = "Paste Lua code. ApiVault scans for common performance hotspots such as OnUpdate, NewTicker, full scans and allocation-heavy patterns.",
		compat = "Paste API names such as C_Item.GetItemInfo. ApiVault reports documented/runtime/source-usage evidence.",
		snippets = "Type a keyword such as savedvariables, event, onupdate, hooksecurefunc, slash or scroll. Leave blank to show all safe-pattern snippets.",
		export = "Click Run to generate a complete developer session export from current ApiVault runtime/debug records.",
	}
	return help[mode] or help.addon
end

function Panel:GetSample(mode)
	mode = mode or self.mode or "addon"
	local samples = {
		addon = "StockVault",
		errors = "Interface/AddOns/StockVault/Core.lua:42: attempt to index field 'settings' (a nil value)\nInterface/AddOns/StockVault/Core.lua:42: in function `RefreshSettings'\nInterface/AddOns/StockVault/Core.lua:88: in function <Interface/AddOns/StockVault/Core.lua:80>",
		sv = "StockVault",
		taint = "button:SetAttribute(\"type\", \"macro\")\nRegisterStateDriver(frame, \"visibility\", \"[combat] hide; show\")\nif InCombatLockdown() then return end",
		package = "## Interface: 120007\n## Title: StockVault\n## Author: DruxlyeSofty\n## Version: 1.0.0\n## SavedVariables: StockVaultDB\n\nCore.lua\nUI/MainFrame.lua",
		performance = "frame:SetScript(\"OnUpdate\", function()\n    for bag = 0, 5 do\n        for slot = 1, C_Container.GetContainerNumSlots(bag) do\n            local info = C_Container.GetContainerItemInfo(bag, slot)\n            table.insert(items, info)\n        end\n    end\nend)",
		compat = "C_Item.GetItemInfo\nC_Container.GetContainerItemInfo\nC_AddOns.GetAddOnMetadata",
		snippets = "savedvariables",
		export = "",
	}
	return samples[mode] or ""
end

function Panel:Create()
	if self.frame then return end

	local frame = createBackdropFrame("ApiVaultDeveloperMustHaveToolsPanel", UIParent)
	self.frame = frame
	frame:SetSize(1120, 760)
	frame:SetPoint("CENTER", 0, 0)
	frame:SetFrameStrata("FULLSCREEN_DIALOG")
	frame:SetMovable(true)
	frame:EnableMouse(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart", frame.StartMoving)
	frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
	if frame.SetResizable then
		frame:SetResizable(true)
		if frame.SetResizeBounds then frame:SetResizeBounds(920, 620)
		elseif frame.SetMinResize then frame:SetMinResize(920, 620) end
	end
	setBackdrop(frame)
	if ApiVault.PrepareWindow then ApiVault:PrepareWindow(frame, "MustHaveDeveloperTools") end
	frame:Hide()

	local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -6, -6)

	local title = createText(frame, "GameFontHighlightLarge")
	title:SetPoint("TOPLEFT", 18, -18)
	title:SetText("ApiVault - Developer Tools")

	local sub = createText(frame, "GameFontDisableSmall")
	self.subText = sub
	sub:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
	sub:SetPoint("RIGHT", frame, "RIGHT", -24, 0)
	sub:SetWordWrap(false)
	sub:SetText(self:GetHelp("addon"))

	local run = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	run:SetSize(80, 22)
	run:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -66)
	run:SetText("Run")
	run:SetScript("OnClick", function() Panel:RunTool() end)

	local sample = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	sample:SetSize(90, 22)
	sample:SetPoint("LEFT", run, "RIGHT", 6, 0)
	sample:SetText("Sample")
	sample:SetScript("OnClick", function() Panel:AddSample() end)

	local clear = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	clear:SetSize(76, 22)
	clear:SetPoint("LEFT", sample, "RIGHT", 6, 0)
	clear:SetText("Clear")
	clear:SetScript("OnClick", function() Panel:SetInput(""); Panel:ShowHelp() end)

	local help = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	help:SetSize(76, 22)
	help:SetPoint("LEFT", clear, "RIGHT", 6, 0)
	help:SetText("Help")
	help:SetScript("OnClick", function() Panel:ShowHelp() end)

	local inputLabel = createText(frame, "GameFontNormalSmall")
	self.inputLabel = inputLabel
	inputLabel:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -96)
	inputLabel:SetText("Input / filter / pasted text")

	local inputBg = createBackdropFrame(nil, frame)
	self.inputBg = inputBg
	inputBg:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -116)
	inputBg:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -18, -116)
	inputBg:SetHeight(112)
	setBackdrop(inputBg, 0.02, 0.02, 0.03, 0.82)

	local inputScroll = CreateFrame("ScrollFrame", nil, inputBg, "UIPanelScrollFrameTemplate")
	self.inputScroll = inputScroll
	inputScroll:SetPoint("TOPLEFT", inputBg, "TOPLEFT", 10, -10)
	inputScroll:SetPoint("BOTTOMRIGHT", inputBg, "BOTTOMRIGHT", -30, 10)

	local input = CreateFrame("EditBox", nil, inputScroll)
	self.input = input
	input:SetMultiLine(true)
	input:SetAutoFocus(false)
	input:SetFontObject(ChatFontNormal)
	input:SetWidth(1020)
	input:SetText("")
	input:SetScript("OnEscapePressed", function(box) box:ClearFocus() end)
	inputScroll:SetScrollChild(input)

	self.toolButtons = {}
	for index, tool in ipairs(TOOLS) do
		local button = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
		button:SetSize(126, 22)
		local column = ((index - 1) % 4)
		local row = math.floor((index - 1) / 4)
		button:SetPoint("TOPLEFT", frame, "TOPLEFT", 18 + (column * 132), -242 - (row * 28))
		button:SetText(tool.label)
		button:SetScript("OnClick", function() Panel:SetMode(tool.key) end)
		self.toolButtons[tool.key] = button
	end

	local outLabel = createText(frame, "GameFontNormalSmall")
	self.outputLabel = outLabel
	outLabel:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -326)
	outLabel:SetText("Output")

	local outBg = createBackdropFrame(nil, frame)
	self.outBg = outBg
	outBg:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -346)
	outBg:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -18, 18)
	setBackdrop(outBg, 0.02, 0.02, 0.03, 0.82)

	local scroll = CreateFrame("ScrollFrame", nil, outBg, "UIPanelScrollFrameTemplate")
	self.scroll = scroll
	scroll:SetPoint("TOPLEFT", outBg, "TOPLEFT", 12, -12)
	scroll:SetPoint("BOTTOMRIGHT", outBg, "BOTTOMRIGHT", -30, 12)

	local output = CreateFrame("EditBox", nil, scroll)
	self.output = output
	output:SetMultiLine(true)
	output:SetAutoFocus(false)
	output:SetFontObject(ChatFontNormal)
	output:SetWidth(1020)
	output:SetText("")
	output:SetScript("OnEscapePressed", function(box) box:ClearFocus() end)
	scroll:SetScrollChild(output)

	frame:SetScript("OnSizeChanged", function()
		if input and inputBg then input:SetWidth(math.max(700, inputBg:GetWidth() - 48)) end
		if output and outBg then output:SetWidth(math.max(700, outBg:GetWidth() - 48)) end
	end)

	self.mode = "addon"
	self:RefreshButtons()
	self:ShowHelp()
end

function Panel:RefreshButtons()
	for _, tool in ipairs(TOOLS) do
		local button = self.toolButtons and self.toolButtons[tool.key]
		if button then
			if self.mode == tool.key then button:Disable() else button:Enable() end
		end
	end
	local tool = self:GetTool()
	if self.subText then self.subText:SetText(self:GetHelp(self.mode)) end
	if self.inputLabel then self.inputLabel:SetText("Input for: " .. tostring(tool.label)) end
	if self.outputLabel then self.outputLabel:SetText("Output: " .. tostring(tool.label)) end
end

function Panel:SetMode(mode)
	self.mode = mode or "addon"
	self:RefreshButtons()
	self:ShowHelp()
end

function Panel:SetInput(text)
	if self.input then
		self.input:SetText(tostring(text or ""))
		self.input:SetCursorPosition(0)
	end
end

function Panel:GetInput()
	return self.input and self.input:GetText() or ""
end

function Panel:SetOutput(text)
	text = tostring(text or "")
	if ApiVault.CapOutputText then text = ApiVault:CapOutputText(text, 900, 90000) end
	self.output:SetText(text)
	self.output:SetCursorPosition(0)
end

function Panel:ShowHelp()
	local tool = self:GetTool()
	local lines = {}
	table.insert(lines, tostring(tool.label))
	table.insert(lines, "")
	table.insert(lines, self:GetHelp(self.mode))
	table.insert(lines, "")
	table.insert(lines, "Click Sample for a tool-specific example, then Run.")
	table.insert(lines, "Output is copyable: click output, Ctrl+A, Ctrl+C.")
	self:SetOutput(table.concat(lines, "\n"))
end

function Panel:AddSample()
	self:SetInput(self:GetSample(self.mode))
	if self.mode == "errors" and ApiVault.ErrorInbox then
		ApiVault.ErrorInbox:Ingest({
			addon = "StockVault",
			message = "Interface/AddOns/StockVault/Core.lua:42: attempt to index field 'settings' (a nil value)",
			stack = self:GetSample("errors"),
			source = "ApiVault DevTools sample",
			file = "Interface/AddOns/StockVault/Core.lua",
			line = 42,
		})
	end
	self:RunTool()
end

function Panel:MaybeIngestPastedError(text)
	if not looksLikeError(text) or not ApiVault.ErrorInbox then return nil end
	local addon = string.match(text, "Interface/AddOns/([^/%s:]+)/") or "PastedError"
	local first = firstLine(text)
	local record = ApiVault.ErrorInbox:Ingest({
		addon = addon,
		message = first,
		stack = text,
		source = "DevTools pasted error",
		file = string.match(text, "([%w_%-%.%/\\]+%.lua):%d+") or "",
		line = tonumber(string.match(text, "%.lua:(%d+)")),
	})
	return record
end

function Panel:FormatErrorMode(text)
	local pasted = self:MaybeIngestPastedError(text)
	local filter = looksLikeError(text) and "" or text
	local list = ApiVault.ErrorInbox and ApiVault.ErrorInbox:FormatList(filter) or "Error inbox unavailable."
	local record = pasted
	if not record and ApiVault.ErrorInbox and ApiVault.ErrorInbox.GetLatestMatch then
		record = ApiVault.ErrorInbox:GetLatestMatch(filter)
	end
	local lines = { list }
	if record and ApiVault.ErrorInbox and ApiVault.ErrorInbox.Explain then
		table.insert(lines, "")
		table.insert(lines, "------------------------------")
		table.insert(lines, ApiVault.ErrorInbox:Explain(record))
	end
	return table.concat(lines, "\n")
end

function Panel:RunTool()
	self:Create()
	local text = self:GetInput()
	local mode = self.mode or "addon"

	if mode == "addon" then
		self:SetOutput(ApiVault.AddonLookup and ApiVault.AddonLookup:FormatAddonReport(text) or "Addon lookup unavailable.")
	elseif mode == "errors" then
		self:SetOutput(self:FormatErrorMode(text))
	elseif mode == "sv" then
		self:SetOutput(ApiVault.SavedVariablesSchemaInspector and ApiVault.SavedVariablesSchemaInspector:FormatReport(text) or "SavedVariables inspector unavailable.")
	elseif mode == "taint" then
		self:SetOutput(ApiVault.TaintCombatSafetyAssistant and ApiVault.TaintCombatSafetyAssistant:FormatReport(text) or "Taint assistant unavailable.")
	elseif mode == "package" then
		self:SetOutput(ApiVault.AddonPackageValidator and ApiVault.AddonPackageValidator:FormatReport(text) or "Package validator unavailable.")
	elseif mode == "performance" then
		self:SetOutput(ApiVault.PerformanceAuditAssistant and ApiVault.PerformanceAuditAssistant:FormatReport(text) or "Performance assistant unavailable.")
	elseif mode == "compat" then
		self:SetOutput(ApiVault.APICompatibilityMatrix and ApiVault.APICompatibilityMatrix:FormatReport(text) or "API compatibility matrix unavailable.")
	elseif mode == "snippets" then
		self:SetOutput(ApiVault.SnippetLibrary and ApiVault.SnippetLibrary:Format(text) or "Snippet library unavailable.")
	elseif mode == "export" then
		self:SetOutput(ApiVault.DeveloperSessionExport and ApiVault.DeveloperSessionExport:BuildReport() or "Session export unavailable.")
	else
		self:ShowHelp()
	end
end

function Panel:Open(mode)
	self:Create()
	if mode then self.mode = mode end
	self:RefreshButtons()
	self.frame:Show()
	if ApiVault.RaiseWindow then ApiVault:RaiseWindow(self.frame) end
	if trim(self:GetInput()) == "" then self:ShowHelp() else self:RunTool() end
end

function Panel:Toggle(mode)
	self:Create()
	if self.frame:IsShown() then self.frame:Hide() else self:Open(mode) end
end
