-- ApiVault Error Inbox + Explain This Error. Added in v1.2.0 must-have tooling rebuild.
-- Data-only; does not execute external code and does not read source files at runtime.
local addonName, ApiVault = ...

ApiVault.ErrorInbox = ApiVault.ErrorInbox or {}
local Inbox = ApiVault.ErrorInbox

Inbox.errors = Inbox.errors or {}
Inbox.byFingerprint = Inbox.byFingerprint or {}
Inbox.nextId = Inbox.nextId or 1
Inbox.maxErrors = 250

local function now()
	return GetTime and GetTime() or 0
end

local function wallTime()
	return date and date("%Y-%m-%d %H:%M:%S") or tostring(now())
end

local function clamp(value, limit)
	value = tostring(value or "")
	limit = limit or 12000
	if #value > limit then
		return string.sub(value, 1, limit) .. "\n<truncated by ApiVault ErrorInbox>"
	end
	return value
end

local function normalize(message)
	message = tostring(message or "")
	message = string.gsub(message, "%d%d?:%d%d?:%d%d?", "<time>")
	message = string.gsub(message, "line %d+", "line <n>")
	message = string.gsub(message, ":%d+:", ":<n>:")
	return message
end

function Inbox:Fingerprint(record)
	return tostring(record.addon or "UNKNOWN") .. "|" .. normalize(record.message or "") .. "|" .. tostring(record.file or "")
end

function Inbox:Ingest(record)
	record = record or {}
	local item = {
		addon = clamp(record.addon or record.addonName or record.owner or "UNKNOWN_ADDON", 256),
		message = clamp(record.message or record.error or "unknown error", 12000),
		stack = clamp(record.stack or "", 24000),
		source = clamp(record.source or "ErrorInbox", 256),
		file = clamp(record.file or "", 1024),
		line = tonumber(record.line) or nil,
		context = clamp(record.context or "", 2000),
		firstSeen = wallTime(),
		lastSeen = wallTime(),
		count = 1,
	}
	item.fingerprint = self:Fingerprint(item)

	local existing = self.byFingerprint[item.fingerprint]
	if existing then
		existing.count = (existing.count or 1) + 1
		existing.lastSeen = wallTime()
		existing.stack = item.stack ~= "" and item.stack or existing.stack
		existing.source = item.source
		existing.context = item.context
		if ApiVault.BugAdvisor and ApiVault.BugAdvisor.Analyze then
			existing.advice = ApiVault.BugAdvisor:Analyze(existing)
		end
		return existing, "deduplicated"
	end

	item.id = self.nextId or 1
	self.nextId = item.id + 1

	if ApiVault.BugAdvisor and ApiVault.BugAdvisor.Analyze then
		item.advice = ApiVault.BugAdvisor:Analyze(item)
	end
	if ApiVault.BugAdvisor and ApiVault.BugAdvisor.ResolveLinks then
		item.links = ApiVault.BugAdvisor:ResolveLinks(item, item.advice)
	end
	if ApiVault.BugAdvisor and ApiVault.BugAdvisor.BuildFixChecklist and item.advice then
		item.fixChecklist = ApiVault.BugAdvisor:BuildFixChecklist(item.advice.category, item.links)
	end

	table.insert(self.errors, 1, item)
	self.byFingerprint[item.fingerprint] = item
	while #self.errors > (self.maxErrors or 250) do
		local old = table.remove(self.errors)
		if old and old.fingerprint then
			self.byFingerprint[old.fingerprint] = nil
		end
	end
	return item, "ingested"
end

function Inbox:Explain(record)
	if not record then
		return "No error selected."
	end
	local category = record.advice and record.advice.category or (ApiVault.BugAdvisor and ApiVault.BugAdvisor.Classify and ApiVault.BugAdvisor:Classify(record.message, record.stack)) or "uncategorized"
	local lines = {}
	table.insert(lines, "Explain This Error")
	table.insert(lines, "id: " .. tostring(record.id or ""))
	table.insert(lines, "addon: " .. tostring(record.addon or ""))
	table.insert(lines, "category: " .. tostring(category))
	table.insert(lines, "occurrences: " .. tostring(record.count or 1))
	table.insert(lines, "firstSeen: " .. tostring(record.firstSeen or ""))
	table.insert(lines, "lastSeen: " .. tostring(record.lastSeen or ""))
	table.insert(lines, "")
	table.insert(lines, "Message:")
	table.insert(lines, tostring(record.message or ""))
	table.insert(lines, "")
	table.insert(lines, "Likely meaning:")
	if category == "nil-value/default-initialization" then
		table.insert(lines, "A table or field is nil before the code initializes defaults. Check SavedVariables, module load order and nested table creation.")
	elseif category == "call-nil/missing-method-or-load-order" then
		table.insert(lines, "The code tried to call a function that is nil. Check TOC order, missing module files, API availability and optional dependency loading.")
	elseif category == "bad-argument/api-signature" then
		table.insert(lines, "An API/function was called with an unexpected argument type/count. Compare the call with ApiVault API metadata.")
	elseif category == "protected/taint" or category == "secure-ui/combat-sensitive" then
		table.insert(lines, "The error is secure/protected/taint-sensitive. Do not fix by forcing execution. Separate secure paths and test OOC/combat.")
	elseif category == "xml-template/missing-template" then
		table.insert(lines, "A template or XML frame reference may be missing or unavailable. Check XML Template Catalog and TOC dependencies.")
	elseif category == "savedvariables/schema" then
		table.insert(lines, "SavedVariables schema/defaults/migration may be inconsistent. Check declarations and defensive default creation.")
	else
		table.insert(lines, "Category is uncertain. Use stack, linked symbols and source context before changing code.")
	end
	table.insert(lines, "")
	table.insert(lines, "Checklist:")
	local checklist = record.fixChecklist or (ApiVault.BugAdvisor and ApiVault.BugAdvisor.BuildFixChecklist and ApiVault.BugAdvisor:BuildFixChecklist(category, record.links)) or {}
	if #checklist == 0 and ApiVault.BugAdvisor and ApiVault.BugAdvisor.BuildChecklist then
		checklist = ApiVault.BugAdvisor:BuildChecklist(category)
	end
	for _, item in ipairs(checklist or {}) do
		table.insert(lines, tostring(item))
	end
	table.insert(lines, "")
	table.insert(lines, "Linked metadata:")
	local links = record.links or {}
	local anyLink = false
	if links.apis and #links.apis > 0 then anyLink = true; table.insert(lines, "APIs: " .. table.concat(links.apis, ", ")) end
	if links.events and #links.events > 0 then anyLink = true; table.insert(lines, "Events: " .. table.concat(links.events, ", ")) end
	if links.templates and #links.templates > 0 then anyLink = true; table.insert(lines, "Templates: " .. table.concat(links.templates, ", ")) end
	if links.modules and #links.modules > 0 then anyLink = true; table.insert(lines, "Modules: " .. table.concat(links.modules, ", ")) end
	if links.securePatterns and #links.securePatterns > 0 then anyLink = true; table.insert(lines, "Secure: " .. table.concat(links.securePatterns, ", ")) end
	if not anyLink then
		table.insert(lines, "No direct metadata links detected.")
	end
	table.insert(lines, "")
	table.insert(lines, "UNCERTAIN: Explanation is generated from error text/stack metadata. Validate against addon source and live behavior.")
	return table.concat(lines, "\n")
end

function Inbox:GetErrors()
	return self.errors or {}
end

function Inbox:GetErrorById(id)
	id = tonumber(id)
	for _, record in ipairs(self.errors or {}) do
		if record.id == id then return record end
	end
end

function Inbox:FormatList(filter)
	filter = string.lower(tostring(filter or ""))
	local lines = {}
	table.insert(lines, "ApiVault Error Inbox")
	table.insert(lines, "errors: " .. tostring(#(self.errors or {})))
	table.insert(lines, "")
	local shown = 0
	for _, record in ipairs(self.errors or {}) do
		local hay = string.lower(tostring(record.addon or "") .. " " .. tostring(record.message or "") .. " " .. tostring(record.source or ""))
		if filter == "" or string.find(hay, filter, 1, true) then
			local category = record.advice and record.advice.category or "uncategorized"
			table.insert(lines, "#" .. tostring(record.id) .. " x" .. tostring(record.count or 1) .. " [" .. tostring(category) .. "] " .. tostring(record.addon) .. " - " .. tostring(record.message))
			shown = shown + 1
		end
	end
	if #(self.errors or {}) == 0 then
		table.insert(lines, "No errors ingested yet. Use ApiVault.ErrorInbox:Ingest({...}) or Public Developer API ReportError.")
	elseif shown == 0 then
		table.insert(lines, "No matching errors for filter: " .. tostring(filter))
		table.insert(lines, "Tip: use the Addon Lookup tool if you typed an addon name such as StockVault.")
	end
	return table.concat(lines, "\n")
end

function Inbox:CopyGitHubIssue(record)
	if not record then return "No error selected." end
	return table.concat({
		"## Bug report",
		"",
		"**Addon:** " .. tostring(record.addon or ""),
		"**ApiVault category:** " .. tostring(record.advice and record.advice.category or "uncategorized"),
		"**Occurrences:** " .. tostring(record.count or 1),
		"",
		"### Error",
		"```text",
		tostring(record.message or ""),
		"```",
		"",
		"### Stack",
		"```text",
		tostring(record.stack or ""),
		"```",
		"",
		"### ApiVault explanation",
		"```text",
		self:Explain(record),
		"```",
	}, "\n")
end

function Inbox:GetLatestMatch(filter)
	filter = string.lower(tostring(filter or ""))
	for _, record in ipairs(self.errors or {}) do
		local hay = string.lower(tostring(record.addon or "") .. " " .. tostring(record.message or "") .. " " .. tostring(record.source or "") .. " " .. tostring(record.stack or ""))
		if filter == "" or string.find(hay, filter, 1, true) then
			return record
		end
	end
end

function Inbox:GetStats()
	return #(self.errors or {}), self.nextId or 1
end

function ApiVault:IngestError(record)
	return ApiVault.ErrorInbox:Ingest(record)
end
