-- Core/Core.lua
-- Shared namespace, saved variables, secret-safe value/name helpers, boot.
-- Dependency-free, pure WoW API. Lua 5.1.

local ADDON, ns = ...

ns.ADDON = ADDON                   -- folder name, e.g. "!!!TweakIdentifier"
local _ver = C_AddOns and C_AddOns.GetAddOnMetadata and C_AddOns.GetAddOnMetadata(ADDON, "Version")
ns.VERSION = (_ver and not _ver:find("@")) and _ver or "2.0.0-dev" -- v1.0.0 unsubstituted in dev

-- 12.0 "Midnight" Secret Values: combat-derived args passed into our hooks may be
-- secret. We must never do arithmetic / comparison / indexing / tostring on them.
local issecret = issecretvalue
ns.HasSecretValues = (issecret ~= nil)

function ns.IsSecret(v)
	if issecret then
		return issecret(v) == true
	end
	return false
end

-- ---------------------------------------------------------------------------
-- Colours / icons / printing
-- ---------------------------------------------------------------------------

local PREFIX = "|cff33ff99TweakID|r: "

ns.COLORS = {
	position   = "ff7fd4ff", size    = "ffffd17f", visibility = "ff9effa0",
	alpha      = "ffd6a0ff", strata  = "ffff9e7f", parent     = "ffff7fb0",
	movable    = "ffd4ff7f", input   = "ff7fffe0", script     = "ffe0e0e0",
	texture    = "ffffc0c0", text    = "ffc0c0ff", font       = "ffb0c4ff",
	create     = "ffffff66", diff    = "ffffa500", animation  = "ff66e0d0",
}

-- Inline texture icons (no emoji — WoW chat/tooltip safe).
ns.ICON = {
	addon    = "|TInterface\\Icons\\INV_Misc_Bag_07:14:14|t",
	blizzard = "|TInterface\\Icons\\INV_Misc_Rune_01:14:14|t",
	unknown  = "|TInterface\\Icons\\INV_Misc_QuestionMark:14:14|t",
	gear     = "|TInterface\\Icons\\Trade_Engineering:14:14|t",
}

function ns.Print(...)
	print(PREFIX .. table.concat({ ... }, " "))
end

function ns.Colorize(hex, text)
	return "|c" .. hex .. tostring(text) .. "|r"
end

-- Wrap an addon name in quotes for readability in logs/UI. Empty/nil → fallback ("?").
function ns.QuoteName(name, fallback)
	if name == nil or name == "" then return fallback or "?" end
	return '"' .. name .. '"'
end

-- ---------------------------------------------------------------------------
-- Object naming / typing
-- ---------------------------------------------------------------------------

function ns.IsAutoName(name)
	if type(name) ~= "string" or name == "" then return true end
	-- Long hex run with no real word = unstable engine name.
	if name:match("%x%x%x%x%x%x%x") and not name:match("%a%a%a%a") then return true end
	return false
end

function ns.ObjectName(obj)
	if ns.IsSecret(obj) or type(obj) ~= "table" then return nil end
	if type(obj.GetName) == "function" then
		local ok, name = pcall(obj.GetName, obj)
		if ok and type(name) == "string" and name ~= "" then return name end
	end
	if type(obj.GetDebugName) == "function" then
		local ok, dbg = pcall(obj.GetDebugName, obj)
		if ok and type(dbg) == "string" and dbg ~= "" then return dbg end
	end
	return nil
end

function ns.ObjectType(obj)
	if not ns.IsSecret(obj) and type(obj) == "table" and type(obj.GetObjectType) == "function" then
		local ok, t = pcall(obj.GetObjectType, obj)
		if ok and t then return t end
	end
	return "?"
end

function ns.IsWidget(obj)
	if ns.IsSecret(obj) then return false end
	return type(obj) == "table" and type(obj.GetObjectType) == "function"
end

-- ---------------------------------------------------------------------------
-- Secret-safe argument stringifier
-- ---------------------------------------------------------------------------

function ns.SafeArg(v)
	if ns.IsSecret(v) then return "|cffff5555<secret>|r" end
	local t = type(v)
	if t == "string" then
		if #v > 48 then return '"' .. v:sub(1, 48) .. '…"' end
		return '"' .. v .. '"'
	elseif t == "number" then
		if v == math.floor(v) and v < 1e15 and v > -1e15 then return tostring(v) end
		return string.format("%.2f", v)
	elseif t == "boolean" or t == "nil" then
		return tostring(v)
	elseif t == "table" then
		local name = ns.ObjectName(v)
		if name then return "[" .. name .. "]" end
		return tostring(v)
	end
	return tostring(v)
end

-- ---------------------------------------------------------------------------
-- Saved variables / settings
-- ---------------------------------------------------------------------------

-- "What to analyze" — all on by default (the user may not know which matters).
ns.CATEGORY_ORDER = {
	"position", "size", "visibility", "alpha", "strata", "parent",
	"movable", "input", "script", "texture", "text", "font", "create", "animation",
}

ns.DEFAULTS = {
	categories = {
		position = true, size = true, visibility = true, alpha = true,
		strata = true, parent = true, movable = true, input = true,
		script = true, texture = true, text = true, font = true, create = true,
		animation = true,
	},
	onlyNonBlizzard    = false,
	chatOutput         = false,
	monitorDescendants = false,
	simpleView         = true,
	throttle           = 0.25,
	maxLog             = 600,
	showTargetOutline  = true,
	numberRange        = 40,
	-- Auto-forget saved investigations not seen in this many days (0 = keep forever).
	-- Pruned at login by Evidence.LoadPersisted using each record's lastSeenAt epoch.
	autoClearDays      = 7,
	-- Deep (source-location) investigation state — see Core/Deep.lua. Persisted so
	-- we never leave the engine CVar on forever, nor disable a user's own setting.
	-- `persist` keeps deep enabled across reloads instead of the default one-session.
	deep = { active = false, enabledByUs = false, wasOnBefore = false, pending = false, persist = false },
	investigations = {}, -- [fingerprintKey] = persisted evidence record
	window = { point = "CENTER", x = 0, y = 0, w = 920, h = 600, scale = 1, shown = false },
}

local function applyDefaults(dst, src)
	for k, v in pairs(src) do
		if type(v) == "table" then
			if type(dst[k]) ~= "table" then dst[k] = {} end
			applyDefaults(dst[k], v)
		elseif dst[k] == nil then
			dst[k] = v
		end
	end
	return dst
end
ns.ApplyDefaults = applyDefaults

local boot = CreateFrame("Frame")

-- Cross-flavor capability: frame source-location tracking (frame:GetSourceLocation +
-- the enableSourceLocationLookup CVar) is a Midnight 12.0 addition — absent on Classic.
-- Probe the method's presence on a real frame (mirrors ns.SourceOf in Attribution.lua);
-- set at file load so Drivers/Deep/Monitor see it. This is the single divider between
-- "can name frame creators" (Retail) and "driver-proxied attribution only" (every flavor).
ns.HasSourceLocation = type(boot.GetSourceLocation) == "function"

boot:RegisterEvent("ADDON_LOADED")
boot:RegisterEvent("PLAYER_LOGIN")
boot:RegisterEvent("PLAYER_LOGOUT")
boot:SetScript("OnEvent", function(self, event, arg1)
	if event == "ADDON_LOADED" and arg1 == ADDON then
		TweakIdentifierDB = TweakIdentifierDB or {}
		ns.db = applyDefaults(TweakIdentifierDB, ns.DEFAULTS)
		ns.db.onlyExternal = nil  -- legacy key, renamed to onlyNonBlizzard (default now off)
		self:UnregisterEvent("ADDON_LOADED")
	elseif event == "PLAYER_LOGIN" then
		ns.db = ns.db or applyDefaults(TweakIdentifierDB or {}, ns.DEFAULTS)
		if ns.Deep and ns.Deep.OnLogin then ns.Deep.OnLogin() end
		if ns.Evidence and ns.Evidence.LoadPersisted then ns.Evidence.LoadPersisted() end
		ns.Print(("v%s loaded. |cffffff00/twid|r to open, |cffffff00/twid pick|r to identify a frame.")
			:format(ns.VERSION))
	elseif event == "PLAYER_LOGOUT" then
		if ns.Evidence and ns.Evidence.SaveAll then ns.Evidence.SaveAll() end
	end
end)
