local addonName, ApiVault = ...

ApiVault.UI = ApiVault.UI or {}

local ROW_HEIGHT = 22
local ROW_COUNT = 21
local FILTERS = { "All", "Function", "Event", "Table" }

local function createFont(parent, template, justify)
	local font = parent:CreateFontString(nil, "OVERLAY", template or "GameFontNormal")
	font:SetJustifyH(justify or "LEFT")
	return font
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.05, g or 0.05, b or 0.08, a or 0.96)
end

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

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

	local frame = createBackdropFrame("ApiVaultFrame", UIParent)
	self.frame = frame
	frame:SetSize(1160, 720)
	frame:SetPoint("CENTER")
	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(1040, 640)
		elseif frame.SetMinResize then
			frame:SetMinResize(1040, 640)
		end
	end
	setBackdrop(frame)
	if ApiVault.PrepareWindow then
		ApiVault:PrepareWindow(frame, "MainFrame")
	end
	frame:Hide()

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

	local interfaceButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	interfaceButton:SetSize(150, 24)
	interfaceButton:SetPoint("TOPRIGHT", close, "TOPLEFT", -10, -2)
	interfaceButton:SetText("Interface Browser")
	interfaceButton:SetScript("OnClick", function()
		if ApiVault.InterfaceBrowser and ApiVault.InterfaceBrowser.Toggle then
			ApiVault.InterfaceBrowser:Toggle()
		else
			ApiVault:Print("interface browser unavailable.")
		end
	end)

	local modulesButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	modulesButton:SetSize(112, 24)
	modulesButton:SetPoint("RIGHT", interfaceButton, "LEFT", -6, 0)
	modulesButton:SetText("Modules")
	modulesButton:SetScript("OnClick", function()
		if ApiVault.ModuleBrowser and ApiVault.ModuleBrowser.Toggle then
			ApiVault.ModuleBrowser:Toggle()
		else
			ApiVault:Print("module browser unavailable.")
		end
	end)

	local examplesButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	examplesButton:SetSize(124, 24)
	examplesButton:SetPoint("RIGHT", modulesButton, "LEFT", -6, 0)
	examplesButton:SetText("Examples")
	examplesButton:SetScript("OnClick", function()
		if ApiVault.SourceExampleBrowser and ApiVault.SourceExampleBrowser.Toggle then
			ApiVault.SourceExampleBrowser:Toggle()
		else
			ApiVault:Print("source example browser unavailable.")
		end
	end)

	local globalSearchButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	globalSearchButton:SetSize(124, 24)
	globalSearchButton:SetPoint("RIGHT", examplesButton, "LEFT", -6, 0)
	globalSearchButton:SetText("Global Search")
	globalSearchButton:SetScript("OnClick", function()
		if ApiVault.UnifiedSearch and ApiVault.UnifiedSearch.Toggle then
			ApiVault.UnifiedSearch:Toggle()
		else
			ApiVault:Print("global search unavailable.")
		end
	end)


	local favoritesButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	favoritesButton:SetSize(104, 24)
	favoritesButton:SetPoint("RIGHT", globalSearchButton, "LEFT", -6, 0)
	favoritesButton:SetText("Fav/Recent")
	favoritesButton:SetScript("OnClick", function()
		if ApiVault.FavoritesRecentPanel and ApiVault.FavoritesRecentPanel.Toggle then
			ApiVault.FavoritesRecentPanel:Toggle()
		else
			ApiVault:Print("favorites/recent unavailable.")
		end
	end)

	local title = createFont(frame, "GameFontHighlightLarge")
	title:SetPoint("TOPLEFT", 18, -16)
	title:SetPoint("RIGHT", favoritesButton, "LEFT", -16, 0)
	title:SetWordWrap(false)
	title:SetText("ApiVault - Blizzard API Browser & Test Runner")

	local meta = createFont(frame, "GameFontDisableSmall")
	meta:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -3)
	meta:SetPoint("RIGHT", title, "RIGHT", 0, 0)
	meta:SetWordWrap(false)
	local dataMeta = ApiVault.APIData and ApiVault.APIData.metadata or {}
	meta:SetText("TOC " .. tostring(ApiVault.interface) .. " / source " .. tostring(dataMeta.source_interface_label or ApiVault.sourceInterfaceLabel) .. " - docs " .. tostring(dataMeta.documentation_files or "?") .. " - functions " .. tostring(dataMeta.functions or "?") .. " - events " .. tostring(dataMeta.events or "?") .. " - tables " .. tostring(dataMeta.tables or "?"))

	local searchBox = CreateFrame("EditBox", nil, frame, "InputBoxTemplate")
	self.searchBox = searchBox
	searchBox:SetSize(330, 26)
	searchBox:SetPoint("TOPLEFT", frame, "TOPLEFT", 22, -62)
	searchBox:SetAutoFocus(false)
	searchBox:SetText("")
	searchBox:SetScript("OnTextChanged", function()
		ApiVault.UI:RefreshSearch()
	end)
	searchBox:SetScript("OnEnterPressed", function(selfBox)
		selfBox:ClearFocus()
	end)

	local placeholder = createFont(frame, "GameFontDisableSmall")
	placeholder:SetPoint("LEFT", searchBox, "RIGHT", 10, 0)
	placeholder:SetText("Search namespace, function, event, table")

	self.filter = "All"
	self.filterButtons = {}
	local prev
	for _, filterName in ipairs(FILTERS) do
		local button = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
		button:SetSize(76, 22)
		if prev then
			button:SetPoint("LEFT", prev, "RIGHT", 4, 0)
		else
			button:SetPoint("TOPLEFT", searchBox, "BOTTOMLEFT", 0, -8)
		end
		button:SetText(filterName)
		button:SetScript("OnClick", function()
			ApiVault.UI.filter = filterName
			ApiVault.UI:RefreshFilterButtons()
			ApiVault.UI:RefreshSearch()
		end)
		self.filterButtons[filterName] = button
		prev = button
	end

	local listTitle = createFont(frame, "GameFontNormalSmall")
	listTitle:SetPoint("TOPLEFT", frame, "TOPLEFT", 22, -120)
	listTitle:SetText("API items")

	local listBg = createBackdropFrame(nil, frame)
	self.listBg = listBg
	listBg:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -138)
	listBg:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 16, 42)
	listBg:SetWidth(390)
	setBackdrop(listBg, 0.02, 0.02, 0.03, 0.82)

	local scroll = CreateFrame("ScrollFrame", "ApiVaultResultScrollFrame", frame, "FauxScrollFrameTemplate")
	self.scroll = scroll
	scroll:SetPoint("TOPLEFT", listBg, "TOPLEFT", 10, -10)
	scroll:SetPoint("BOTTOMRIGHT", listBg, "BOTTOMRIGHT", -28, 10)
	scroll:SetScript("OnVerticalScroll", function(selfScroll, offset)
		FauxScrollFrame_OnVerticalScroll(selfScroll, offset, ROW_HEIGHT, function()
			ApiVault.UI:RefreshRows()
		end)
	end)

	self.rows = {}
	for index = 1, ROW_COUNT do
		local row = CreateFrame("Button", nil, frame)
		row:SetHeight(ROW_HEIGHT)
		row:SetPoint("TOPLEFT", scroll, "TOPLEFT", 0, -((index - 1) * ROW_HEIGHT))
		row:SetPoint("RIGHT", scroll, "RIGHT", -4, 0)
		row.text = createFont(row, "GameFontHighlightSmall")
		row.text:SetPoint("LEFT", row, "LEFT", 2, 0)
		row.text:SetPoint("RIGHT", row, "RIGHT", -4, 0)
		row.text:SetWordWrap(false)
		row:SetScript("OnClick", function(selfRow)
			ApiVault.UI:SelectEntry(selfRow.entry)
		end)
		row:SetScript("OnEnter", function(selfRow)
			if selfRow.entry then
				GameTooltip:SetOwner(selfRow, "ANCHOR_RIGHT")
				GameTooltip:AddLine(selfRow.entry.display, 1, 1, 1)
				GameTooltip:AddLine(selfRow.entry.kind .. " - " .. selfRow.entry.risk, 0.7, 0.7, 0.7)
				GameTooltip:Show()
			end
		end)
		row:SetScript("OnLeave", function()
			GameTooltip:Hide()
		end)
		self.rows[index] = row
	end

	local detailBg = createBackdropFrame(nil, frame)
	self.detailBg = detailBg
	detailBg:SetPoint("TOPLEFT", listBg, "TOPRIGHT", 14, 0)
	detailBg:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -16, 42)
	setBackdrop(detailBg, 0.025, 0.025, 0.035, 0.92)

	self.detailTitle = createFont(detailBg, "GameFontHighlightLarge")
	self.detailTitle:SetPoint("TOPLEFT", detailBg, "TOPLEFT", 14, -14)
	self.detailTitle:SetPoint("RIGHT", detailBg, "RIGHT", -14, 0)
	self.detailTitle:SetWordWrap(false)
	self.detailTitle:SetText("Select an API item")

	self.detailSub = createFont(detailBg, "GameFontDisableSmall")
	self.detailSub:SetPoint("TOPLEFT", self.detailTitle, "BOTTOMLEFT", 0, -4)
	self.detailSub:SetPoint("RIGHT", detailBg, "RIGHT", -14, 0)
	self.detailSub:SetWordWrap(false)
	self.detailSub:SetText("")

	self.argTitle = createFont(detailBg, "GameFontNormalSmall")
	self.argTitle:SetPoint("TOPLEFT", self.detailSub, "BOTTOMLEFT", 0, -14)
	self.argTitle:SetText("Arguments")
	self.argTitle:Hide()

	self.argRows = {}
	for index = 1, 8 do
		local label = createFont(detailBg, "GameFontHighlightSmall")
		label:SetSize(250, 20)
		if index == 1 then
			label:SetPoint("TOPLEFT", self.argTitle, "BOTTOMLEFT", 0, -6)
		else
			label:SetPoint("TOPLEFT", self.argRows[index - 1].label, "BOTTOMLEFT", 0, -5)
		end
		local input = CreateFrame("EditBox", nil, detailBg, "InputBoxTemplate")
		input:SetHeight(22)
		input:SetPoint("LEFT", label, "RIGHT", 12, 0)
		input:SetPoint("RIGHT", detailBg, "RIGHT", -32, 0)
		input:SetAutoFocus(false)
		input:SetScript("OnEnterPressed", function(selfBox) selfBox:ClearFocus() end)
		label:Hide()
		input:Hide()
		self.argRows[index] = { label = label, input = input }
	end

	self.allowRestricted = CreateFrame("CheckButton", nil, detailBg, "UICheckButtonTemplate")
	self.allowRestricted.text = self.allowRestricted:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
	self.allowRestricted.text:SetPoint("LEFT", self.allowRestricted, "RIGHT", 0, 0)
	self.allowRestricted.text:SetText("Allow restricted/review test out of combat")
	self.allowRestricted:SetScript("OnClick", function(selfButton)
		ApiVault.db.settings.allowRestrictedExecution = selfButton:GetChecked() and true or false
	end)
	self.allowRestricted:Hide()

	self.infoPanel = createBackdropFrame(nil, detailBg)
	setBackdrop(self.infoPanel, 0.01, 0.01, 0.015, 0.74)

	self.infoScroll = CreateFrame("ScrollFrame", nil, self.infoPanel, "UIPanelScrollFrameTemplate")
	self.infoScroll:SetPoint("TOPLEFT", self.infoPanel, "TOPLEFT", 8, -8)
	self.infoScroll:SetPoint("BOTTOMRIGHT", self.infoPanel, "BOTTOMRIGHT", -28, 8)
	self.infoBox = CreateFrame("EditBox", nil, self.infoScroll)
	self.infoBox:SetMultiLine(true)
	self.infoBox:SetAutoFocus(false)
	self.infoBox:SetFontObject(ChatFontNormal)
	self.infoBox:SetWidth(600)
	self.infoBox:SetTextInsets(4, 4, 4, 4)
	self.infoBox:SetScript("OnEscapePressed", function(selfBox) selfBox:ClearFocus() end)
	self.infoScroll:SetScrollChild(self.infoBox)

	self.buttonBar = CreateFrame("Frame", nil, detailBg)
	self.buttonBar:SetSize(520, 24)
	self.buttonBar:SetPoint("BOTTOMRIGHT", detailBg, "BOTTOMRIGHT", -18, 132)

	self.runButton = CreateFrame("Button", nil, self.buttonBar, "UIPanelButtonTemplate")
	self.runButton:SetSize(90, 24)
	self.runButton:SetPoint("LEFT", self.buttonBar, "LEFT", 0, 0)
	self.runButton:SetText("Run Test")
	self.runButton:SetEnabled(false)
	self.runButton:SetScript("OnClick", function()
		ApiVault.UI:RunSelected()
	end)

	self.eventButton = CreateFrame("Button", nil, self.buttonBar, "UIPanelButtonTemplate")
	self.eventButton:SetSize(100, 24)
	self.eventButton:SetPoint("LEFT", self.runButton, "RIGHT", 8, 0)
	self.eventButton:SetText("Listen Event")
	self.eventButton:SetEnabled(false)
	self.eventButton:SetScript("OnClick", function()
		ApiVault.UI:ToggleEventListener()
	end)

	self.exportButton = CreateFrame("Button", nil, self.buttonBar, "UIPanelButtonTemplate")
	self.exportButton:SetSize(70, 24)
	self.exportButton:SetPoint("LEFT", self.eventButton, "RIGHT", 8, 0)
	self.exportButton:SetText("Export")
	self.exportButton:SetEnabled(false)
	self.exportButton:SetScript("OnClick", function()
		if ApiVault.ExportPanel and ApiVault.ExportPanel.OpenWithContext then
			local entry = ApiVault.UI.selectedEntry
			ApiVault.ExportPanel:OpenWithContext(entry and {
				category = "API",
				kind = entry.kind or "",
				display = entry.display or "",
				module = entry.system or "",
				riskClass = entry.risk or "",
				data = entry,
			} or nil, "api-summary")
		else
			ApiVault:Print("export panel unavailable.")
		end
	end)

	self.favButton = CreateFrame("Button", nil, self.buttonBar, "UIPanelButtonTemplate")
	self.favButton:SetSize(72, 24)
	self.favButton:SetPoint("LEFT", self.exportButton, "RIGHT", 8, 0)
	self.favButton:SetText("Favorite")
	self.favButton:SetEnabled(false)
	self.favButton:SetScript("OnClick", function()
		local entry = ApiVault.UI.selectedEntry
		if not entry or not ApiVault.TodoCompletion then return end
		local context = { category = "API", kind = entry.kind or "", display = entry.display or "", module = entry.system or "", riskClass = entry.risk or "", data = entry }
		local added = ApiVault.TodoCompletion:ToggleFavorite(context)
		ApiVault.UI.favButton:SetText(added and "Unfav" or "Favorite")
	end)

	self.testLabButton = CreateFrame("Button", nil, self.buttonBar, "UIPanelButtonTemplate")
	self.testLabButton:SetSize(74, 24)
	self.testLabButton:SetPoint("LEFT", self.favButton, "RIGHT", 8, 0)
	self.testLabButton:SetText("Test Lab")
	self.testLabButton:SetScript("OnClick", function()
		if ApiVault.TestLabPanel and ApiVault.TestLabPanel.Toggle then
			ApiVault.TestLabPanel:Toggle()
		else
			ApiVault:Print("runtime test lab unavailable.")
		end
	end)

	self.resultTitle = createFont(detailBg, "GameFontNormalSmall")
	self.resultTitle:SetPoint("BOTTOMLEFT", detailBg, "BOTTOMLEFT", 14, 112)
	self.resultTitle:SetText("Result / event log")

	self.resultPanel = createBackdropFrame(nil, detailBg)
	self.resultPanel:SetPoint("TOPLEFT", self.resultTitle, "BOTTOMLEFT", 0, -4)
	self.resultPanel:SetPoint("BOTTOMRIGHT", detailBg, "BOTTOMRIGHT", -14, 14)
	setBackdrop(self.resultPanel, 0.01, 0.01, 0.015, 0.70)

	self.resultScroll = CreateFrame("ScrollFrame", nil, self.resultPanel, "UIPanelScrollFrameTemplate")
	self.resultScroll:SetPoint("TOPLEFT", self.resultPanel, "TOPLEFT", 8, -8)
	self.resultScroll:SetPoint("BOTTOMRIGHT", self.resultPanel, "BOTTOMRIGHT", -28, 8)
	self.resultBox = CreateFrame("EditBox", nil, self.resultScroll)
	self.resultBox:SetMultiLine(true)
	self.resultBox:SetAutoFocus(false)
	self.resultBox:SetFontObject(ChatFontNormal)
	self.resultBox:SetWidth(600)
	self.resultBox:SetTextInsets(4, 4, 4, 4)
	self.resultBox:SetScript("OnEscapePressed", function(selfBox) selfBox:ClearFocus() end)
	self.resultScroll:SetScrollChild(self.resultBox)

	self:LayoutDetailPanels()


	if frame.HookScript then
		frame:HookScript("OnSizeChanged", function()
			ApiVault.UI:UpdateScrollChildWidths()
		end)
	end

	self:UpdateScrollChildWidths()
	self:RefreshFilterButtons()
	self:RefreshSearch()
end

function ApiVault.UI:UpdateScrollChildWidths()
	if self.infoScroll and self.infoBox and self.infoScroll.GetWidth then
		local width = self.infoScroll:GetWidth()
		if width and width > 80 then
			self.infoBox:SetWidth(width - 18)
		end
	end
	if self.resultScroll and self.resultBox and self.resultScroll.GetWidth then
		local width = self.resultScroll:GetWidth()
		if width and width > 80 then
			self.resultBox:SetWidth(width - 18)
		end
	end
end

function ApiVault.UI:LayoutDetailPanels(kind, argCount)
	if not self.detailBg or not self.infoPanel then
		return
	end

	local isFunction = kind == "Function"
	argCount = tonumber(argCount or 0) or 0

	if self.allowRestricted then
		self.allowRestricted:ClearAllPoints()
		self.allowRestricted:SetShown(isFunction)
		if isFunction then
			local visibleArg = math.min(argCount, #(self.argRows or {}))
			if visibleArg > 0 and self.argRows[visibleArg] and self.argRows[visibleArg].label then
				self.allowRestricted:SetPoint("TOPLEFT", self.argRows[visibleArg].label, "BOTTOMLEFT", -4, -8)
			else
				self.allowRestricted:SetPoint("TOPLEFT", self.argTitle, "BOTTOMLEFT", -4, -8)
			end
		end
	end

	if self.buttonBar then
		self.buttonBar:ClearAllPoints()
		self.buttonBar:SetPoint("BOTTOMRIGHT", self.detailBg, "BOTTOMRIGHT", -18, 132)
	end

	if self.resultTitle then
		self.resultTitle:ClearAllPoints()
		self.resultTitle:SetPoint("BOTTOMLEFT", self.detailBg, "BOTTOMLEFT", 14, 112)
	end

	if self.resultPanel then
		self.resultPanel:ClearAllPoints()
		self.resultPanel:SetPoint("TOPLEFT", self.resultTitle, "BOTTOMLEFT", 0, -4)
		self.resultPanel:SetPoint("BOTTOMRIGHT", self.detailBg, "BOTTOMRIGHT", -14, 14)
	end

	self.infoPanel:ClearAllPoints()
	if isFunction and self.allowRestricted then
		self.infoPanel:SetPoint("TOPLEFT", self.allowRestricted, "BOTTOMLEFT", 4, -10)
	else
		self.infoPanel:SetPoint("TOPLEFT", self.detailSub, "BOTTOMLEFT", 0, -14)
	end
	self.infoPanel:SetPoint("BOTTOMRIGHT", self.detailBg, "BOTTOMRIGHT", -14, 166)

	self:UpdateScrollChildWidths()
end


function ApiVault.UI:Toggle()
	self:Create()
	if self.frame:IsShown() then
		self.frame:Hide()
	else
		self.frame:Show()
		self.searchBox:SetFocus()
	end
end

function ApiVault.UI:RefreshFilterButtons()
	for name, button in pairs(self.filterButtons or {}) do
		button:SetEnabled(name ~= self.filter)
	end
end

function ApiVault.UI:RefreshSearch()
	if not self.frame then
		return
	end
	local query = self.searchBox and self.searchBox:GetText() or ""
	self.results = ApiVault.Index:Search(query, self.filter or "All", 300)
	self:RefreshRows()
end

function ApiVault.UI:RefreshRows()
	local results = self.results or {}
	local offset = FauxScrollFrame_GetOffset(self.scroll)
	FauxScrollFrame_Update(self.scroll, #results, ROW_COUNT, ROW_HEIGHT)
	for index = 1, ROW_COUNT do
		local resultIndex = offset + index
		local row = self.rows[index]
		local entry = results[resultIndex]
		row.entry = entry
		if entry then
			row:Show()
			local prefix = entry.kind == "Function" and "F " or entry.kind == "Event" and "E " or "T "
			row.text:SetText(prefix .. entry.display .. "  |cff888888" .. entry.risk .. "|r")
		else
			row:Hide()
		end
	end
end



local function getBundledSourceDisplayPath(filePath)
	if ApiVault.SourceUsage and ApiVault.SourceUsage.ToBundledSourcePath then
		return ApiVault.SourceUsage:ToBundledSourcePath(filePath)
	end
	if type(filePath) == "string" and string.sub(filePath, 1, 10) == "Interface/" then
		return "Interface/AddOns/ApiVault/Examples/" .. filePath
	end
	return filePath or "?"
end

local function appendSourceUsageText(text, entry)
	if not entry or not ApiVault.SourceUsage then
		return text
	end
	local usage
	local title
	if entry.kind == "Function" then
		usage = ApiVault.SourceUsage:GetAPIUsage(entry.display)
		title = "Bundled example source usage"
	elseif entry.kind == "Event" then
		usage = ApiVault.SourceUsage:GetEventUsage(entry.display)
		title = "Bundled example event registrations"
	else
		return text
	end

	text = ApiVault:AppendLine(text, "")
	text = ApiVault:AppendLine(text, title .. ":")
	text = ApiVault:AppendLine(text, "- source root: " .. tostring(ApiVault.SourceUsage and ApiVault.SourceUsage.GetBundledSourceRoot and ApiVault.SourceUsage:GetBundledSourceRoot() or "Interface/AddOns/ApiVault/Examples/Interface"))
	if not usage then
		return ApiVault:AppendLine(text, "- no bundled example source references indexed")
	end

	text = ApiVault:AppendLine(text, "- references: " .. tostring(usage.c or 0))
	if usage.m then
		text = ApiVault:AppendLine(text, "- modules: " .. tostring(usage.m))
	end
	if usage.u and usage.u > 0 then
		text = ApiVault:AppendLine(text, "- RegisterUnitEvent references: " .. tostring(usage.u))
	end

	local locations = usage.l or {}
	if #locations == 0 then
		return ApiVault:AppendLine(text, "- no stored bundled example source samples")
	end
	text = ApiVault:AppendLine(text, "- bundled example source samples:")
	for index, loc in ipairs(locations) do
		if index > 12 then
			break
		end
		local filePath = getBundledSourceDisplayPath(loc[1])
		local lineNumber = loc[2] or "?"
		local moduleName = loc[3] or "?"
		local context = loc[4] or ""
		if entry.kind == "Event" then
			local registerKind = loc[4] or "RegisterEvent"
			context = tostring(registerKind) .. " / " .. tostring(loc[5] or "")
		end
		text = ApiVault:AppendLine(text, "  " .. tostring(index) .. ". " .. tostring(filePath) .. ":" .. tostring(lineNumber) .. " [" .. tostring(moduleName) .. "] " .. tostring(context))
	end
	return text
end

local function appendSourceExampleText(text, entry)
	if not entry or not ApiVault.SourceExamples or not ApiVault.SourceExamples.AppendOwnerSummary then
		return text
	end
	if entry.kind ~= "Function" and entry.kind ~= "Event" then
		return text
	end
	return ApiVault.SourceExamples:AppendOwnerSummary(text, entry.display, entry.kind, 5)
end


local function appendSecureReferenceText(text, entry)
	if not entry or not ApiVault.SecurePatterns or not ApiVault.SecurePatterns.AppendOwnerSummary then
		return text
	end
	if entry.kind == "Function" then
		return ApiVault.SecurePatterns:AppendOwnerSummary(text, entry.display, "Function", 5)
	end
	return text
end

function ApiVault.UI:SelectEntry(entry)
	self.selectedEntry = entry
	if ApiVault.TodoCompletion and entry then
		ApiVault.TodoCompletion:SetCurrentContext({ category = "API", kind = entry.kind or "", display = entry.display or "", module = entry.system or "", riskClass = entry.risk or "", data = entry }, "ApiVault.UI")
	end
	if ApiVault.ChecklistExport and ApiVault.ChecklistExport.SetContext and entry then
		ApiVault.ChecklistExport:SetContext({
			category = "API",
			kind = entry.kind or "",
			display = entry.display or "",
			module = entry.system or "",
			riskClass = entry.risk or "",
			data = entry,
		})
	end
	if ApiVault.UserState and entry then
		ApiVault.UserState:AddRecent({
			category = "API",
			kind = entry.kind or "",
			display = entry.display or "",
			module = entry.system or "",
			riskClass = entry.risk or "",
			source = "ApiVault.UI",
		}, "ApiVault.UI")
	end
	if not entry then
		return
	end
	local systemInfo, objectInfo = ApiVault.Index:GetObject(entry)
	self.detailTitle:SetText(entry.display)
	self.detailSub:SetText(entry.kind .. " - risk: " .. tostring(entry.risk) .. " - reason: " .. tostring(entry.riskReason) .. " - source: " .. tostring(systemInfo and systemInfo.file or "?"))

	for _, row in ipairs(self.argRows) do
		row.label:Hide()
		row.input:Hide()
		row.input:SetText("")
	end

	if self.argTitle then
		self.argTitle:SetShown(entry.kind == "Function")
	end

	local text = ApiVault:DocToText(objectInfo and objectInfo.doc)
	text = ApiVault:AppendLine(text, "Execution policy: " .. tostring(entry.risk) .. " (" .. tostring(entry.riskReason) .. ")")
	if ApiVault.UserState then
		local favoriteContext = { category = "API", kind = entry.kind or "", display = entry.display or "", module = entry.system or "", riskClass = entry.risk or "" }
		text = ApiVault:AppendLine(text, "Favorite: " .. (ApiVault.UserState:IsFavorite(favoriteContext) and "yes" or "no") .. " (/av favorites)")
	end
	if ApiVault.Symbols and ApiVault.Symbols.GetForName then
		local symbols = ApiVault.Symbols:GetForName(entry.display, 4)
		if #symbols > 0 then
			text = ApiVault:AppendLine(text, "Symbol coverage: " .. tostring(#symbols) .. " linked symbol record(s). Use /av symbols for full coverage.")
			for _, symbol in ipairs(symbols) do
				text = ApiVault:AppendLine(text, "- " .. tostring(symbol.symbolType or "?") .. " / " .. tostring(symbol.callablePolicy or "inspect-only") .. " / " .. tostring(symbol.module or ""))
			end
		else
			text = ApiVault:AppendLine(text, "Symbol coverage: no source symbol record linked.")
		end
	end

	if entry.kind == "Function" then
		local args = objectInfo.args or {}
		local defaults = ApiVault.Examples:GetDefaults(entry.display, args)
		local remembered = ApiVault.TestHistory and ApiVault.TestHistory.GetRememberedArguments and ApiVault.TestHistory:GetRememberedArguments(entry.display) or nil
		for index = 1, math.min(#args, #self.argRows) do
			local row = self.argRows[index]
			row.label:Show()
			row.input:Show()
			row.label:SetText(ApiVault:FormatParam(args[index]))
			row.input:SetText((remembered and remembered[index]) or defaults[index] or "")
		end
		if #args > #self.argRows then
			text = ApiVault:AppendLine(text, "WARNING: only first " .. tostring(#self.argRows) .. " arguments are editable in this prototype.")
		end
		text = ApiVault:AppendLine(text, "")
		text = ApiVault:AppendLine(text, "Returns:")
		for _, param in ipairs(objectInfo.rets or {}) do
			text = ApiVault:AppendLine(text, "- " .. ApiVault:FormatParam(param))
		end
		if #(objectInfo.rets or {}) == 0 then
			text = ApiVault:AppendLine(text, "- <none documented>")
		end
		if ApiVault.ArgumentBuilder and ApiVault.ArgumentBuilder.Describe then
			text = ApiVault:AppendLine(text, "")
			text = ApiVault:AppendLine(text, ApiVault.ArgumentBuilder:Describe(objectInfo))
		end
		if ApiVault.SafeRunner and ApiVault.SafeRunner.GetPolicyReport then
			local policy = ApiVault.SafeRunner:GetPolicyReport(entry, ApiVault.db and ApiVault.db.settings and ApiVault.db.settings.allowRestrictedExecution)
			text = ApiVault:AppendLine(text, "")
			text = ApiVault:AppendLine(text, policy)
		end
	elseif entry.kind == "Event" then
		text = ApiVault:AppendLine(text, "Payload:")
		for _, param in ipairs(objectInfo.payload or {}) do
			text = ApiVault:AppendLine(text, "- " .. ApiVault:FormatParam(param))
		end
		if #(objectInfo.payload or {}) == 0 then
			text = ApiVault:AppendLine(text, "- <none documented>")
		end
	elseif entry.kind == "Table" then
		text = ApiVault:AppendLine(text, "Fields:")
		for _, param in ipairs(objectInfo.fields or {}) do
			text = ApiVault:AppendLine(text, "- " .. ApiVault:FormatParam(param))
		end
		text = ApiVault:AppendLine(text, "")
		text = ApiVault:AppendLine(text, "Values:")
		for _, param in ipairs(objectInfo.values or {}) do
			text = ApiVault:AppendLine(text, "- " .. ApiVault:FormatParam(param))
		end
	end

	text = ApiVault:AppendLine(text, "")
	text = ApiVault:AppendLine(text, "Example:")
	text = ApiVault:AppendLine(text, ApiVault.Examples:BuildSnippet(entry, systemInfo, objectInfo))
	if ApiVault.DetailLinks and ApiVault.DetailLinks.AppendAPIDetail then
		text = ApiVault.DetailLinks:AppendAPIDetail(text, entry, systemInfo, objectInfo, 8)
		if ApiVault.TodoCompletion and ApiVault.TodoCompletion.AppendRelationshipCard then
			text = ApiVault.TodoCompletion:AppendRelationshipCard(text, { category = "API", kind = entry.kind or "", display = entry.display or "", module = entry.system or "", riskClass = entry.risk or "", data = entry })
		end
	else
		text = appendSourceUsageText(text, entry)
		text = appendSourceExampleText(text, entry)
		text = appendSecureReferenceText(text, entry)
	end

	local layoutArgCount = 0
	if entry.kind == "Function" and objectInfo then
		layoutArgCount = #(objectInfo.args or {})
	end
	self:LayoutDetailPanels(entry.kind, layoutArgCount)
	self.infoBox:SetText(text)
	self.infoBox:SetCursorPosition(0)
	self.resultBox:SetText("")
	local secureBlocked = false
	if ApiVault.SecurePatterns and ApiVault.SecurePatterns.IsBlockedAPI and entry.kind == "Function" then
		secureBlocked = ApiVault.SecurePatterns:IsBlockedAPI(entry.display)
	end
	self.runButton:SetEnabled(entry.kind == "Function" and entry.risk ~= "protected" and entry.risk ~= "metadata" and not secureBlocked)
	self.eventButton:SetEnabled(entry.kind == "Event")
	if self.exportButton then
		self.exportButton:SetEnabled(true)
	end
	if self.favButton then
		self.favButton:SetEnabled(true)
		local favoriteContext = { category = "API", kind = entry.kind or "", display = entry.display or "", module = entry.system or "", riskClass = entry.risk or "" }
		self.favButton:SetText((ApiVault.UserState and ApiVault.UserState:IsFavorite(favoriteContext)) and "Unfav" or "Favorite")
	end
	self.allowRestricted:SetChecked(ApiVault.db.settings.allowRestrictedExecution and true or false)
	self:UpdateScrollChildWidths()
end

function ApiVault.UI:GetRawArgValues()
	local values = {}
	for index, row in ipairs(self.argRows or {}) do
		values[index] = row.input:GetText()
	end
	return values
end

function ApiVault.UI:RunSelected()
	local entry = self.selectedEntry
	local ok, result = ApiVault.SafeRunner:Run(entry, self:GetRawArgValues(), self.allowRestricted:GetChecked())
	if ok then
		self:AppendResult("OK: " .. tostring(entry and entry.display or "<none>") .. "\n" .. tostring(result))
	else
		local policy = ""
		if ApiVault.SafeRunner and ApiVault.SafeRunner.GetPolicyReport then
			policy = "\n\n" .. tostring((ApiVault.SafeRunner:GetPolicyReport(entry, self.allowRestricted:GetChecked())))
		end
		self:AppendResult("ERROR: " .. tostring(result) .. policy)
	end
	if ApiVault.TestLabPanel and ApiVault.TestLabPanel.frame and ApiVault.TestLabPanel.frame:IsShown() then
		ApiVault.TestLabPanel:Refresh()
	end
end

function ApiVault.UI:ToggleEventListener()
	local entry = self.selectedEntry
	if not entry or entry.kind ~= "Event" then
		self:AppendResult("ERROR: select an Event first.")
		return
	end
	local _, objectInfo = ApiVault.Index:GetObject(entry)
	local eventName = objectInfo and (objectInfo.literal or objectInfo.n) or entry.display
	local ok, message = ApiVault.EventTester:Toggle(eventName)
	self:AppendResult((ok and "OK: " or "ERROR: ") .. tostring(message))
end

function ApiVault.UI:AppendResult(text)
	if not self.resultBox then
		ApiVault:Print(text)
		return
	end
	local oldText = self.resultBox:GetText() or ""
	local newText = tostring(text)
	if oldText ~= "" then
		newText = newText .. "\n---\n" .. oldText
	end
	self.resultBox:SetText(newText)
	self.resultBox:SetCursorPosition(0)
end
