local addonName, ApiVault = ...

ApiVault.UnifiedSearch = ApiVault.UnifiedSearch or {}

local ROW_HEIGHT = 22
local ROW_COUNT = 24
local FILTERS = { "All", "API", "Function", "Event", "Table", "Resource", "XML", "Module", "Example", "Secure", "Symbol" }
local function getSearchFilters()
	if ApiVault and ApiVault.TodoCompletion and ApiVault.TodoCompletion.GetFilterList then
		return ApiVault.TodoCompletion:GetFilterList()
	end
	return FILTERS
end

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.04, g or 0.04, b or 0.06, a or 0.96)
end

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

local function resultPrefix(result)
	if not result then
		return "? "
	end
	local category = result.category
	if category == "API" then
		if result.kind == "Function" then return "F " end
		if result.kind == "Event" then return "E " end
		if result.kind == "Table" then return "T " end
		return "A "
	elseif category == "Resource" then
		return "R "
	elseif category == "XML" then
		return "X "
	elseif category == "Module" then
		return "M "
	elseif category == "Example" then
		return "Ex "
	elseif category == "Secure" then
		return "S "
	end
	return "? "
end

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

	local frame = createBackdropFrame("ApiVaultUnifiedSearchFrame", UIParent)
	self.frame = frame
	frame:SetSize(1120, 700)
	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(900, 540)
		elseif frame.SetMinResize then
			frame:SetMinResize(900, 540)
		end
	end
	setBackdrop(frame)
	if ApiVault.PrepareWindow then
		ApiVault:PrepareWindow(frame, "UnifiedSearch")
	end
	frame:Hide()

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

	local title = createFont(frame, "GameFontHighlightLarge")
	title:SetPoint("TOPLEFT", 18, -16)
	title:SetText("ApiVault - Unified Developer Search")

	local layers, counts = 0, {}
	if ApiVault.GlobalSearch and ApiVault.GlobalSearch.GetStats then
		layers, counts = ApiVault.GlobalSearch:GetStats()
	end
	local sub = createFont(frame, "GameFontDisableSmall")
	sub:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
	sub:SetText("metadata layers " .. tostring(layers) .. " - APIs " .. tostring((counts.apiFunctions or 0) + (counts.apiEvents or 0) + (counts.apiTables or 0)) .. " - resources " .. tostring(counts.resourceFiles or 0) .. " - XML " .. tostring(counts.xmlTemplates or 0) .. " - modules " .. tostring(counts.moduleTOCs or 0) .. " - examples " .. tostring(counts.sourceExamples or 0) .. " - secure " .. tostring(counts.securePatterns or 0))

	local search = CreateFrame("EditBox", nil, frame, "InputBoxTemplate")
	self.searchBox = search
	search:SetSize(390, 24)
	search:SetPoint("TOPLEFT", 18, -62)
	search:SetAutoFocus(false)
	search:SetScript("OnEnterPressed", function(box)
		box:ClearFocus()
		if ApiVault.UserState then
			ApiVault.UserState:RecordSearch(box:GetText(), ApiVault.UnifiedSearch.filter or "All")
		end
		ApiVault.UnifiedSearch:RefreshSearch()
	end)
	search:SetScript("OnTextChanged", function()
		ApiVault.UnifiedSearch:ScheduleRefresh()
	end)

	local clear = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	clear:SetSize(62, 22)
	clear:SetPoint("LEFT", search, "RIGHT", 8, 0)
	clear:SetText("Clear")
	clear:SetScript("OnClick", function()
		search:SetText("")
		ApiVault.UnifiedSearch:RefreshSearch()
	end)

	local open = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	self.openButton = open
	open:SetSize(128, 22)
	open:SetPoint("LEFT", clear, "RIGHT", 8, 0)
	open:SetText("Open Related")
	open:SetScript("OnClick", function()
		ApiVault.UnifiedSearch:OpenRelated()
	end)

	local export = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	self.exportButton = export
	export:SetSize(86, 22)
	export:SetPoint("LEFT", open, "RIGHT", 8, 0)
	export:SetText("Export")
	export:SetScript("OnClick", function()
		if ApiVault.ExportPanel and ApiVault.ExportPanel.OpenWithContext then
			ApiVault.ExportPanel:OpenWithContext(ApiVault.UnifiedSearch.selectedResult, "audit-note")
		else
			ApiVault:Print("export panel unavailable.")
		end
	end)

	local fav = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	fav:SetSize(96, 22)
	fav:SetPoint("LEFT", export, "RIGHT", 8, 0)
	fav:SetText("Fav/Recent")
	fav:SetScript("OnClick", function()
		if ApiVault.FavoritesRecentPanel and ApiVault.FavoritesRecentPanel.Toggle then
			ApiVault.FavoritesRecentPanel:Toggle()
		else
			ApiVault:Print("favorites/recent unavailable.")
		end
	end)

	local actions = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	actions:SetSize(74, 22)
	actions:SetPoint("LEFT", fav, "RIGHT", 8, 0)
	actions:SetText("Actions")
	actions:SetScript("OnClick", function()
		if ApiVault.TodoCompletionPanel and ApiVault.TodoCompletionPanel.Toggle then
			ApiVault.TodoCompletionPanel:Toggle()
		else
			ApiVault:Print("developer actions panel unavailable.")
		end
	end)

	local note = createFont(frame, "GameFontDisableSmall")
	note:SetPoint("LEFT", actions, "RIGHT", 10, 0)
	note:SetText("Search all generated metadata. No source files are read at runtime.")

	self.filter = "All"
	self.filterButtons = {}
	local filters = getSearchFilters()
	local firstButton
	for index, filterName in ipairs(filters) do
		local button = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
		local width = filterName == "HasExamples" and 96 or filterName == "HasSecure" and 88 or filterName == "SourceBacked" and 106 or filterName == "InspectOnly" and 94 or filterName == "Function" and 78 or filterName == "Resource" and 78 or filterName == "Example" and 74 or 66
		button:SetSize(width, 21)
		local row = math.floor((index - 1) / 10)
		local col = (index - 1) % 10
		if col == 0 then
			button:SetPoint("TOPLEFT", search, "BOTTOMLEFT", 0, -9 - (row * 25))
		else
			button:SetPoint("LEFT", self.filterButtons[filters[index - 1]], "RIGHT", 4, 0)
		end
		button:SetText(filterName)
		button:SetScript("OnClick", function()
			ApiVault.UnifiedSearch.filter = filterName
			ApiVault.UnifiedSearch:RefreshFilterButtons()
			ApiVault.UnifiedSearch:RefreshSearch()
		end)
		self.filterButtons[filterName] = button
		firstButton = firstButton or button
	end

	local listBg = createBackdropFrame(nil, frame)
	self.listBg = listBg
	listBg:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -152)
	listBg:SetSize(430, 520)
	setBackdrop(listBg, 0.02, 0.02, 0.03, 0.82)

	self.resultCount = createFont(frame, "GameFontNormalSmall")
	self.resultCount:SetPoint("BOTTOMLEFT", listBg, "TOPLEFT", 4, 4)
	self.resultCount:SetText("Results")

	local scroll = CreateFrame("ScrollFrame", "ApiVaultUnifiedSearchScrollFrame", 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.UnifiedSearch: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.UnifiedSearch:SelectResult(selfRow.result)
		end)
		row:SetScript("OnEnter", function(selfRow)
			if selfRow.result then
				GameTooltip:SetOwner(selfRow, "ANCHOR_RIGHT")
				GameTooltip:AddLine(tostring(selfRow.result.display), 1, 1, 1)
				GameTooltip:AddLine(tostring(selfRow.result.category) .. " / " .. tostring(selfRow.result.kind), 0.7, 0.7, 0.7)
				GameTooltip:AddLine("risk: " .. tostring(selfRow.result.riskClass), 0.8, 0.6, 0.4)
				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, 18)
	setBackdrop(detailBg, 0.025, 0.025, 0.035, 0.86)

	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 a result")

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

	self.detailScroll = CreateFrame("ScrollFrame", nil, detailBg, "UIPanelScrollFrameTemplate")
	self.detailScroll:SetPoint("TOPLEFT", self.detailSub, "BOTTOMLEFT", 0, -14)
	self.detailScroll:SetPoint("BOTTOMRIGHT", detailBg, "BOTTOMRIGHT", -30, 14)
	self.detailBox = CreateFrame("EditBox", nil, self.detailScroll)
	self.detailBox:SetMultiLine(true)
	self.detailBox:SetAutoFocus(false)
	self.detailBox:SetFontObject(ChatFontNormal)
	self.detailBox:SetWidth(620)
	self.detailBox:SetTextInsets(4, 4, 4, 4)
	self.detailBox:SetScript("OnEscapePressed", function(box) box:ClearFocus() end)
	self.detailScroll:SetScrollChild(self.detailBox)

	self:RefreshFilterButtons()
	self:RefreshSearch()
end

function ApiVault.UnifiedSearch:OpenSearch(query, filter)
	self:Create()
	if not self.frame:IsShown() then
		self.frame:Show()
	end
	if ApiVault.RaiseWindow then
		ApiVault:RaiseWindow(self.frame)
	end
	if filter and self.filterButtons and self.filterButtons[filter] then
		self.filter = filter
		self:RefreshFilterButtons()
	end
	self.searchBox:SetText(query or "")
	if ApiVault.UserState then
		ApiVault.UserState:RecordSearch(query or "", self.filter or "All")
	end
	self:RefreshSearch()
	self.searchBox:SetFocus()
end

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

function ApiVault.UnifiedSearch:ScheduleRefresh()
	if self.refreshPending then
		return
	end
	self.refreshPending = true
	if C_Timer and C_Timer.After then
		C_Timer.After(0.08, function()
			if ApiVault.UnifiedSearch then
				ApiVault.UnifiedSearch.refreshPending = nil
				ApiVault.UnifiedSearch:RefreshSearch()
			end
		end)
	else
		self.refreshPending = nil
		self:RefreshSearch()
	end
end

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

function ApiVault.UnifiedSearch:RefreshSearch()
	if not self.frame then
		return
	end
	local query = self.searchBox and self.searchBox:GetText() or ""
	if ApiVault.GlobalSearch and ApiVault.GlobalSearch.Search then
		self.results = ApiVault.GlobalSearch:Search(query, self.filter or "All", 350)
	else
		self.results = {}
	end
	self.resultCount:SetText("Results: " .. tostring(#(self.results or {})) .. " / filter " .. tostring(self.filter or "All"))
	if type(FauxScrollFrame_SetOffset) == "function" then
		FauxScrollFrame_SetOffset(self.scroll, 0)
	end
	self:RefreshRows()
	if self.results and self.results[1] and not self.selectedResult then
		self:SelectResult(self.results[1])
	end
end

function ApiVault.UnifiedSearch: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 result = results[resultIndex]
		row.result = result
		if result then
			row:Show()
			local risk = result.riskClass and result.riskClass ~= "" and (" |cff888888" .. tostring(result.riskClass) .. "|r") or ""
			local suffix = ApiVault.TodoCompletion and ApiVault.TodoCompletion.GetRowSuffix and ApiVault.TodoCompletion:GetRowSuffix(result) or ""
			row.text:SetText(resultPrefix(result) .. tostring(result.display or "?") .. " |cff999999[" .. tostring(result.category or "?") .. "]|r" .. risk .. suffix)
		else
			row:Hide()
		end
	end
end

function ApiVault.UnifiedSearch:SelectResult(result)
	self.selectedResult = result
	if ApiVault.TodoCompletion and result then
		ApiVault.TodoCompletion:SetCurrentContext(result, "UnifiedSearch")
	end
	if ApiVault.ChecklistExport and ApiVault.ChecklistExport.SetContext and result then
		ApiVault.ChecklistExport:SetContext(result)
	end
	if ApiVault.UserState and result then
		ApiVault.UserState:AddRecent(result, "UnifiedSearch")
	end
	if not result then
		return
	end
	self.detailTitle:SetText(tostring(result.display or "?"))
	self.detailSub:SetText(tostring(result.category or "?") .. " / " .. tostring(result.kind or "?") .. " / module " .. tostring(result.module or ""))
	local text = "No detail available."
	if ApiVault.GlobalSearch and ApiVault.GlobalSearch.DescribeResult then
		text = ApiVault.GlobalSearch:DescribeResult(result)
	end
	if ApiVault.UserState then
		text = ApiVault:AppendLine(text, "")
		text = ApiVault:AppendLine(text, "Favorite: " .. (ApiVault.UserState:IsFavorite(result) and "yes" or "no") .. " (/av favorites)")
	end
	self.detailBox:SetText(text)
	self.detailBox:SetCursorPosition(0)
end

function ApiVault.UnifiedSearch:OpenRelated()
	local result = self.selectedResult
	if not result then
		ApiVault:Print("select a search result first.")
		return
	end
	if result.category == "API" then
		if ApiVault.UI and ApiVault.UI.Toggle then ApiVault.UI:Toggle() end
	elseif result.category == "Resource" then
		if ApiVault.InterfaceBrowser and ApiVault.InterfaceBrowser.Toggle then ApiVault.InterfaceBrowser:Toggle() end
	elseif result.category == "XML" then
		if ApiVault.XMLTemplateBrowser and ApiVault.XMLTemplateBrowser.Toggle then ApiVault.XMLTemplateBrowser:Toggle() end
	elseif result.category == "Module" then
		if ApiVault.ModuleBrowser and ApiVault.ModuleBrowser.Toggle then ApiVault.ModuleBrowser:Toggle() end
	elseif result.category == "Example" then
		if ApiVault.SourceExampleBrowser and ApiVault.SourceExampleBrowser.Toggle then ApiVault.SourceExampleBrowser:Toggle() end
	elseif result.category == "Secure" then
		if ApiVault.SecurePatternBrowser and ApiVault.SecurePatternBrowser.Toggle then ApiVault.SecurePatternBrowser:Toggle() end
	else
		ApiVault:Print("no related browser for " .. tostring(result.category))
	end
end
