-- wow_mock.lua
-- ============================================================================
-- Minimal mock of the WoW client API surface for headless tests.
-- The mock is INFRASTRUCTURE -- its job is to let production code run in a
-- headless Lua interpreter, not to validate behavior. Test assertions live
-- in the test_*.lua files. To avoid forcing production code to defensively
-- guard `if widget.SetMethod then widget:SetMethod(...)`, any PascalCase
-- method we don't explicitly stub auto-resolves to a no-op via __index
-- (defined at end of file as `LATTICE_MOCK_AUTO_NOOP_MT`).

_G = _G or _ENV

-- Lua 5.2+ removed `unpack` global (table.unpack only). WoW exposes both;
-- mirror that here so production BE compat-shim works in headless tests.
unpack = unpack or table.unpack

-- ===== Globals =====
SlashCmdList = SlashCmdList or {}
UIParent = UIParent or {}
function UnitName(_unit) return "Tester" end
function GetLocale() return "enUS" end
function time() return 1234567890 end
function CreateColor(r, g, b, a) return { r = r, g = g, b = b, a = a } end

-- ===== C_Timer =====
C_Timer = C_Timer or {}
local _timerDeferred = false
local _timerQueue = {}
function C_Timer.After(_delay, fn)
    if _timerDeferred then
        _timerQueue[#_timerQueue + 1] = fn
    else
        fn()
    end
end
function MockTimerDeferred(on) _timerDeferred = on and true or false end
function MockTimerDrain()
    local q = _timerQueue; _timerQueue = {}
    for _, fn in ipairs(q) do fn() end
end

-- ===== Auto-stub infrastructure =====
-- DEEP_STUB is a single object that is BOTH callable (returns itself) AND
-- infinitely indexable (any field returns itself). The auto-stub metatable
-- applied to frame/fontstring/texture objects returns DEEP_STUB for any
-- PascalCase access that wasn't explicitly stubbed. Effect: production code
-- can call/index any WoW frame API without defensive `if X.Y then` guards.
local DEEP_STUB = {}
local DEEP_STUB_MT = {
    __index = function() return DEEP_STUB end,
    __call  = function() return DEEP_STUB end,
}
setmetatable(DEEP_STUB, DEEP_STUB_MT)

local function applyAutoNoop(obj)
    setmetatable(obj, { __index = function(_t, k)
        if type(k) == "string" and k:sub(1, 1):match("[A-Z]") then
            return DEEP_STUB
        end
    end })
    return obj
end

-- Backwards alias: newFontString applies auto-noop too.
local applyAutoNoopFS = applyAutoNoop

-- ===== Frame mock =====
local function newFrame()
    local f = {
        _children = {},
        _events = {},
        _scripts = {},
    }
    function f:SetSize(_w, _h) end
    function f:SetWidth(_w) end
    function f:SetHeight(_h) end
    function f:SetPoint(...) end
    function f:SetAllPoints(_) end
    function f:SetParent(_p) end
    function f:GetParent() return self._parent end
    function f:Show() self._shown = true end
    function f:Hide() self._shown = false end
    function f:IsShown() return self._shown end
    function f:SetFrameStrata(_s) end
    function f:SetMovable(_b) end
    function f:EnableMouse(_b) end
    function f:RegisterForDrag(_s) end
    function f:RegisterForClicks(_s) end
    function f:RegisterEvent(e) self._events[e] = true end
    function f:UnregisterEvent(e) self._events[e] = nil end
    function f:SetScript(name, fn) self._scripts[name] = fn end
    function f:GetScript(name) return self._scripts[name] end
    function f:HookScript(name, fn) self._scripts[name] = fn end
    function f:SetFontString(_fs) end          -- Button method
    function f:SetEnabled(_b) end              -- Button method
    function f:SetText(_t) self._text = _t end -- Button method
    function f:GetText() return self._text or "" end
    function f:SetAutoFocus(_b) end            -- EditBox method
    function f:SetMaxLetters(_n) end           -- EditBox method
    function f:ClearFocus() end                -- EditBox method
    function f:HasFocus() return false end     -- EditBox method
    function f:SetFontObject(_) end            -- Button/EditBox method
    function f:SetNormalAtlas(_) end           -- Button method
    function f:SetHighlightAtlas(_) end        -- Button method
    function f:SetPushedAtlas(_) end           -- Button method
    function f:GetNormalTexture() return self:CreateTexture() end
    function f:GetHighlightTexture() return self:CreateTexture() end
    function f:SetHighlightTexture(_) end
    function f:SetPushedTexture(_) end
    function f:GetFrameLevel() return 100 end
    function f:SetFrameLevel(_n) end
    function f:SetClampedToScreen(_b) end
    function f:SetToplevel(_b) end
    function f:SetClipsChildren(_b) end
    function f:EnableMouseWheel(_b) end
    function f:StartMoving() end
    function f:StopMovingOrSizing() end
    function f:GetLeft() return 0 end
    function f:GetTop() return 0 end
    function f:GetBottom() return 0 end
    function f:GetRight() return 0 end
    function f:SetAlpha(_a) end
    function f:GetAlpha() return 1 end
    function f:IsVisible() return self._shown end
    function f:CreateFontString(_a, _layer)
        local fs = newFontString()
        return fs
    end
    function f:CreateTexture(_a, _layer)
        local t = { _isTexture = true }
        function t:SetTexture(_) end
        function t:SetAtlas(_) end
        function t:SetSize(_, _) end
        function t:SetWidth(_) end
        function t:SetHeight(_) end
        function t:SetPoint(...) end
        function t:ClearAllPoints() end
        function t:SetAllPoints(_) end
        function t:SetVertexColor(_, _, _, _) end
        function t:SetAlpha(_) end
        function t:SetDesaturated(_) end
        function t:SetVertexOffset(_, _, _) end
        function t:SetColorTexture(_, _, _, _) end
        function t:SetTexCoord(_, _, _, _) end
        function t:SetBlendMode(_) end
        function t:SetGradient(_, _, _) end
        function t:Show() end function t:Hide() end
        return t
    end
    function f:CreateMaskTexture()
        local m = {}
        function m:SetAllPoints(_) end
        function m:SetAtlas(_) end
        return m
    end
    function f:AddMaskTexture(_) end
    function f:SetBackdrop(_) end
    function f:SetBackdropColor(_, _, _, _) end
    function f:SetBackdropBorderColor(_, _, _, _) end
    function f:GetWidth() return 100 end
    function f:GetHeight() return 100 end
    return f
end

function newFontString()
    local fs = { _text = "" }
    function fs:SetText(t) self._text = tostring(t or "") end
    function fs:GetText() return self._text end
    function fs:SetPoint(...) end
    function fs:ClearAllPoints() end
    function fs:SetSize(_, _) end
    function fs:SetWidth(_) end
    function fs:SetHeight(_) end
    function fs:SetFontObject(_) end
    function fs:SetTextColor(_, _, _, _) end
    function fs:SetJustifyH(_) end
    function fs:SetJustifyV(_) end
    function fs:SetWordWrap(_) end
    function fs:GetStringWidth() return (#self._text * 6) end
    function fs:Show() end function fs:Hide() end
    -- FontStrings get the same auto-stub treatment as Frames so production
    -- code can call any PascalCase method/field without guards. See the
    -- DEEP_STUB block below CreateFrame for details.
    return applyAutoNoopFS(fs)
end

function CreateFrame(_kind, _name, _parent, _template)
    local f = newFrame()
    f._parent = _parent
    return applyAutoNoop(f)
end

function hooksecurefunc(_name, _fn) end

-- ===== WowScrollBox factories (real WoW: FrameXML globals) =====
local function newScrollBoxView()
    local v = {}
    function v:SetElementExtent(_) end
    function v:SetElementExtentCalculator(_) end
    function v:SetElementFactory(_) end
    function v:SetElementInitializer(_, _) end
    function v:SetElementResetter(_) end
    function v:SetPanExtent(_) end
    function v:RegisterCallback(_, _) end
    return v
end
function CreateScrollBoxListLinearView() return newScrollBoxView() end
function CreateScrollBoxListTreeListView() return newScrollBoxView() end
function ScrollUtil() end
ScrollUtil = { InitScrollBoxListWithScrollBar = function() end }

-- ===== Font globals =====
GameFontNormal      = "GameFontNormal"
GameFontNormalLarge = "GameFontNormalLarge"
GameFontNormalHuge  = "GameFontNormalHuge"
GameFontNormalSmall = "GameFontNormalSmall"
GameFontNormalTiny  = "GameFontNormalTiny"
