local _, GF = ...

GF.SearchMemoryDiagnostics = GF.SearchMemoryDiagnostics or {}
local Diagnostics = GF.SearchMemoryDiagnostics

local function chat(message)
	if not message or message == "" then
		return
	end
	if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
		DEFAULT_CHAT_FRAME:AddMessage(tostring(message))
	elseif type(print) == "function" then
		print(tostring(message))
	end
end

local function number(value)
	return tonumber(value) or 0
end

local function formatSummary(capture)
	local resultInfoReads = number(capture.resultInfoReads)
	local sortReads = number(capture.sortResultInfoReads)
	local entryReads = number(capture.entryResultInfoReads)
	local authoritativeReads = number(capture.authoritativeResultInfoReads)
	local liveReads = number(capture.liveResultInfoReads)
	local otherReads = math.max(
		0,
		resultInfoReads - sortReads - entryReads - authoritativeReads - liveReads
	)
	return string.format(
		"[GFMem] #%d scopes=%d/%d raw=%d kept=%d final=%d "
			.. "reuse=%d fresh=%d result=%d[%d/%d/%d/%d/%d] "
			.. "entries=%d counts=%d/%d[%d] "
			.. "sort=%d/%d wrappers=%d/%d/%d compact=%d",
		number(capture.sequence),
		number(capture.scopesCompleted),
		number(capture.scopesExpected),
		number(capture.rawResults),
		number(capture.keptResults),
		number(capture.finalResults),
		number(capture.reusedSearchInfo),
		number(capture.freshSearchInfo),
		resultInfoReads,
		sortReads,
		entryReads,
		authoritativeReads,
		liveReads,
		otherReads,
		number(capture.entriesHydrated),
		number(capture.searchMemberCountReads),
		number(capture.resultMemberCountReads),
		number(capture.sortMemberCountReads),
		number(capture.sortCacheHits),
		number(capture.sortKeys),
		number(capture.wrappersCreated),
		number(capture.wrappersReused),
		number(capture.wrappersPruned),
		number(capture.summariesCompacted)
	)
end

function Diagnostics:IsEnabled()
	return self.enabled == true
end

function Diagnostics:IsCapturing()
	return self.enabled == true and type(self.current) == "table"
end

function Diagnostics:SetEnabled(enabled)
	self.enabled = enabled == true
	self.current = nil
	self.last = nil
	self.sequence = 0
	return self.enabled
end

function Diagnostics:BeginSearch(scopesExpected)
	if not self:IsEnabled() then
		return nil
	end
	if self.current then
		self:AbortSearch("replaced")
	end
	self.sequence = number(self.sequence) + 1
	local capture = {
		sequence = self.sequence,
		scopesExpected = number(scopesExpected),
	}
	self.current = capture
	return capture.sequence
end

function Diagnostics:Add(field, amount)
	local capture = self.current
	if self.enabled ~= true or type(capture) ~= "table"
		or type(field) ~= "string"
	then
		return false
	end
	capture[field] = number(capture[field]) + number(amount == nil and 1 or amount)
	return true
end

function Diagnostics:Set(field, value)
	local capture = self.current
	if self.enabled ~= true or type(capture) ~= "table"
		or type(field) ~= "string"
	then
		return false
	end
	capture[field] = number(value)
	return true
end

function Diagnostics:RecordScope(nativeTotal, rawCount, keptCount, reusedCount)
	if not self:IsCapturing() then
		return false
	end
	self:Add("scopesCompleted", 1)
	self:Add("nativeTotal", nativeTotal)
	self:Add("rawResults", rawCount)
	self:Add("keptResults", keptCount)
	self:Add("reusedSearchInfo", reusedCount)
	return true
end

function Diagnostics:RecordWrappers(created, reused, pruned)
	if not self:IsCapturing() then
		return false
	end
	self:Add("wrappersCreated", created)
	self:Add("wrappersReused", reused)
	self:Add("wrappersPruned", pruned)
	return true
end

function Diagnostics:CompleteSearch(expectedCapture, finalCount)
	local capture = self.current
	if not self:IsCapturing() or capture ~= expectedCapture then
		return nil
	end
	capture.finalResults = number(finalCount)
	capture.completionScheduled = nil
	local summary = formatSummary(capture)
	capture.summary = summary
	self.last = capture
	self.current = nil
	chat(summary)
	return summary
end

function Diagnostics:ScheduleCompleteSearch(finalCount)
	local capture = self.current
	if not self:IsCapturing() then
		return false
	end
	capture.finalResults = number(finalCount)
	if capture.completionScheduled then
		return true
	end
	capture.completionScheduled = true
	if C_Timer and type(C_Timer.After) == "function" then
		C_Timer.After(0, function()
			Diagnostics:CompleteSearch(capture, capture.finalResults)
		end)
	else
		self:CompleteSearch(capture, capture.finalResults)
	end
	return true
end

function Diagnostics:AbortSearch(reason)
	local capture = self.current
	if not self:IsCapturing() then
		return false
	end
	self.current = nil
	capture.abortReason = tostring(reason or "aborted")
	self.last = capture
	chat(string.format(
		"[GFMem] #%d aborted=%s",
		number(capture.sequence),
		capture.abortReason
	))
	return true
end

function Diagnostics:GetLastSummary()
	return self.last and self.last.summary or nil
end

function Diagnostics:PrintStatus()
	if self:IsEnabled() then
		chat("[GFMem] ON session-only; no forced GC; /gf memdiag off to stop.")
	else
		chat("[GFMem] OFF; /gf memdiag on to start.")
	end
end

function Diagnostics:PrintLast()
	chat(self:GetLastSummary() or "[GFMem] no completed search captured.")
end
