-- ApiVault Bug Advisor. Added in v1.1.0.
-- Classifies error records supplied as data. Does not read source files or execute code.
local addonName, ApiVault = ...

ApiVault.BugAdvisor = ApiVault.BugAdvisor or {}
local Advisor = ApiVault.BugAdvisor

local function lower(value)
	return string.lower(tostring(value or ""))
end

local function findAPIs(text)
	local out, seen = {}, {}
	for namespace, name in string.gmatch(tostring(text or ""), "(C_[%w_]+)%.([%w_]+)") do
		local api = namespace .. "." .. name
		if not seen[api] then
			seen[api] = true
			table.insert(out, api)
		end
	end
	return out
end

local function findEvents(text)
	local out, seen = {}, {}
	for token in string.gmatch(tostring(text or ""), "%f[%u]([A-Z][A-Z0-9_]+)%f[%A]") do
		if string.find(token, "_", 1, true) and #token >= 5 and not seen[token] then
			seen[token] = true
			table.insert(out, token)
		end
	end
	return out
end

local SECURE_PATTERNS = {
	"SetAttribute",
	"GetAttribute",
	"RegisterStateDriver",
	"UnregisterStateDriver",
	"SecureActionButtonTemplate",
	"SecureHandler",
	"SecureUnitButtonTemplate",
	"SecureFrameTemplate",
	"InCombatLockdown",
	"hooksecurefunc",
}

local function findSecure(text)
	local out = {}
	local hay = tostring(text or "")
	for _, pattern in ipairs(SECURE_PATTERNS) do
		if string.find(hay, pattern, 1, true) then
			table.insert(out, pattern)
		end
	end
	return out
end

function Advisor:Classify(message, stack)
	local text = lower(tostring(message or "") .. "\n" .. tostring(stack or ""))
	if string.find(text, "attempt to index", 1, true) or string.find(text, "nil value", 1, true) then
		return "nil-value/default-initialization"
	end
	if string.find(text, "attempt to call", 1, true) and string.find(text, "nil", 1, true) then
		return "call-nil/missing-method-or-load-order"
	end
	if string.find(text, "bad argument", 1, true) or string.find(text, "usage:", 1, true) then
		return "bad-argument/api-signature"
	end
	if string.find(text, "action blocked", 1, true) or string.find(text, "taint", 1, true) or string.find(text, "protected", 1, true) then
		return "protected/taint"
	end
	if string.find(text, "setattribute", 1, true) or string.find(text, "incombatlockdown", 1, true) then
		return "secure-ui/combat-sensitive"
	end
	if string.find(text, "template", 1, true) and (string.find(text, "not found", 1, true) or string.find(text, "unknown", 1, true)) then
		return "xml-template/missing-template"
	end
	if string.find(text, "registerevent", 1, true) or string.find(text, "event", 1, true) then
		return "event-registration/payload"
	end
	if string.find(text, "savedvariables", 1, true) or string.find(text, "db", 1, true) then
		return "savedvariables/schema"
	end
	return "uncategorized"
end

function Advisor:BuildSuggestions(category)
	local suggestions = {
		["nil-value/default-initialization"] = {
			"Check whether the table is initialized before use.",
			"Check ADDON_LOADED order and SavedVariables defaults.",
			"Add defensive default creation before indexing nested fields.",
			"Use ApiVault's SavedVariables checklist and addon audit mode.",
		},
		["call-nil/missing-method-or-load-order"] = {
			"Check whether the function/method exists in this build and load state.",
			"Check TOC file order and dependencies.",
			"If the function belongs to another module/addon, verify that module is loaded first.",
			"Use Symbol Browser and Loaded Addon Inspector to compare source/runtime availability.",
		},
		["bad-argument/api-signature"] = {
			"Compare the call with documented ApiVault API arguments/returns.",
			"Use Test Lab with safe arguments only.",
			"Add type checks before the API call.",
			"Mark cache/context-dependent APIs as requiring in-game validation.",
		},
		["protected/taint"] = {
			"Treat this as secure-sensitive.",
			"Do not fix by forcing protected execution.",
			"Separate secure click paths from normal UI state.",
			"Test out-of-combat and in combat.",
		},
		["secure-ui/combat-sensitive"] = {
			"Create/update secure attributes only through valid secure patterns.",
			"Do not call SetAttribute automatically from ApiVault.",
			"Create secure frames out of combat where applicable.",
			"Use Secure Pattern Reference Index for inspect-only guidance.",
		},
		["xml-template/missing-template"] = {
			"Check XML Template Catalog for exact template name.",
			"Check whether the Blizzard module containing the template is loaded.",
			"Check TOC dependencies and file order.",
			"Use a safe fallback template or create a simple frame if the template is optional.",
		},
		["event-registration/payload"] = {
			"Check exact event literal spelling.",
			"Compare payloads with API documentation.",
			"Use Event Lab to record real payloads.",
			"Cap event logs and avoid registering all events.",
		},
		["savedvariables/schema"] = {
			"Check .toc SavedVariables declaration.",
			"Initialize defaults after ADDON_LOADED.",
			"Add schema version and migration guard.",
			"Do not assume existing user data has new fields.",
		},
		uncategorized = {
			"Inspect stack trace and linked symbols.",
			"Search ApiVault for referenced APIs, templates and modules.",
			"Add defensive logging around the failing code path.",
			"Mark diagnosis as UNCERTAIN until source context is available.",
		},
	}
	return suggestions[category] or suggestions.uncategorized
end

function Advisor:BuildChecklist(category)
	local base = {
		"[ ] Confirm the exact error message and first stack frame.",
		"[ ] Confirm addon load order and relevant TOC dependencies.",
		"[ ] Search ApiVault for referenced APIs/templates/modules/symbols.",
		"[ ] Check whether the issue changes after /reload.",
	}
	if category == "protected/taint" or category == "secure-ui/combat-sensitive" then
		table.insert(base, "[ ] Test out of combat.")
		table.insert(base, "[ ] Test in combat.")
		table.insert(base, "[ ] Verify no insecure UI state touches secure frames.")
	end
	if category == "bad-argument/api-signature" then
		table.insert(base, "[ ] Compare arguments with documented API metadata.")
		table.insert(base, "[ ] Test safe read-only call with controlled arguments.")
	end
	if category == "savedvariables/schema" or category == "nil-value/default-initialization" then
		table.insert(base, "[ ] Verify SavedVariables are declared and initialized.")
		table.insert(base, "[ ] Verify defaults/migrations before nested table access.")
	end
	return base
end

function Advisor:Analyze(errorRecord)
	errorRecord = errorRecord or {}
	local message = tostring(errorRecord.message or errorRecord.error or "")
	local stack = tostring(errorRecord.stack or "")
	local combined = message .. "\n" .. stack
	local category = self:Classify(message, stack)
	return {
		category = category,
		apis = findAPIs(combined),
		events = findEvents(combined),
		securePatterns = findSecure(combined),
		suggestions = self:BuildSuggestions(category),
		checklist = self:BuildChecklist(category),
		uncertainNotes = "Generated from error text only. Full source context is required for a definitive fix.",
	}
end

function Advisor:FormatAdvice(errorRecord)
	local advice = self:Analyze(errorRecord)
	local lines = {}
	table.insert(lines, "ApiVault Bug Advisor")
	table.insert(lines, "category: " .. tostring(advice.category))
	table.insert(lines, "message: " .. tostring(errorRecord and errorRecord.message or ""))
	table.insert(lines, "")
	if #advice.apis > 0 then
		table.insert(lines, "linked APIs: " .. table.concat(advice.apis, ", "))
	end
	if #advice.events > 0 then
		table.insert(lines, "linked events: " .. table.concat(advice.events, ", "))
	end
	if #advice.securePatterns > 0 then
		table.insert(lines, "linked secure patterns: " .. table.concat(advice.securePatterns, ", "))
	end
	table.insert(lines, "")
	table.insert(lines, "suggestions:")
	for _, suggestion in ipairs(advice.suggestions or {}) do
		table.insert(lines, "- " .. tostring(suggestion))
	end
	table.insert(lines, "")
	table.insert(lines, "checklist:")
	for _, item in ipairs(advice.checklist or {}) do
		table.insert(lines, item)
	end
	table.insert(lines, "")
	table.insert(lines, "UNCERTAIN: " .. tostring(advice.uncertainNotes))
	return table.concat(lines, "\n")
end

function Advisor:GetStats()
	return 8, #SECURE_PATTERNS
end
