-- Test: bootstrap lifecycle discipline (ADR-020).
--
-- Verifies that `BootHDGREngine()` (the test helper that mirrors the real
-- Init.lua sequence) wires all four phases correctly, in the documented
-- order. This is the canonical alternative to `HDG.Modules:Boot()` (the
-- legacy convenience that skips engine interleaving).
--
-- The skill (~/.claude/skills/redux-style-wow-addon/testing.md §Test
-- bootstrap discipline) requires every full-stack smoke test to use
-- BootHDGREngine, not Modules:Boot. This test validates the helper itself.

dofile("wow_mock.lua")
dofile("../Core/HDGR_SchemeConstants.lua")
dofile("../Core/HDGR_Constants.lua")
dofile("../Core/HDGR_Actions.lua")
dofile("../Core/HDGR_Resolvers.lua")
dofile("../Core/HDGR_Format.lua")
dofile("../Core/HDGR_Paths.lua")
dofile("../Core/HDGR_Log.lua")
dofile("../Core/HDGR_Memo.lua")
dofile("../Core/HDGR_StoreFurnishings.lua")
dofile("../Core/HDGR_Store.lua")
dofile("../Core/HDGR_Middleware.lua")
dofile("../Core/HDGR_Environment.lua")
dofile("../Core/HDGR_BlizzardEvents.lua")
dofile("../Core/HDGR_Modules.lua")
dofile("../Core/HDGR_WidgetTypes.lua")
dofile("../UI/HDGR_Selectors.lua")

local function assertEq(a, e, label)
    if a ~= e then error(label .. ": expected " .. tostring(e) .. ", got " .. tostring(a), 2) end
end
local function assertTrue(v, label) if not v then error(label, 2) end end
local function assertNotNil(v, label) if v == nil then error(label .. ": expected non-nil", 2) end end

-- ===== 1. Helper exists =====================================================

assertTrue(type(_G.BootHDGREngine) == "function",
    "BootHDGREngine helper must be defined in wow_mock.lua")

-- ===== 2. Phase-order instrumentation =======================================
-- Hook the engine entry points so we can record the sequence they fire in.
-- After BootHDGREngine() runs, the recorded sequence must match the documented
-- order from Init.lua (and wow_mock.lua:264-274).

local sequence = {}
local function record(name)
    sequence[#sequence + 1] = name
end

-- Wrap the engine methods. We can't replace them because BootHDGREngine reads
-- them by reference -- so we hook each by saving original + reassigning.
local origBEInit  = HDG.BlizzardEvents.OnInitialize
local origBEBoot  = HDG.BlizzardEvents.Boot
local origTopo    = HDG.Modules.Topo
local origP1      = HDG.Modules.Phase1
local origP2      = HDG.Modules.Phase2
local origEnvDec  = HDG.Environment.DeclareAll
local origEnvBld  = HDG.Environment.Build
local origMWApply = HDG.Middleware.Apply
local origWTFlush = HDG.WidgetTypes.Flush

HDG.BlizzardEvents.OnInitialize = function(self, ...) record("BE:Init"); return origBEInit(self, ...) end
HDG.BlizzardEvents.Boot         = function(self, ...) record("BE:Boot"); return origBEBoot(self, ...) end
HDG.Modules.Topo                = function(self, ...) record("Topo");    return origTopo(self, ...) end
HDG.Modules.Phase1              = function(self, ...) record("Phase1");  return origP1(self, ...) end
HDG.Modules.Phase2              = function(self, ...) record("Phase2");  return origP2(self, ...) end
HDG.Environment.DeclareAll      = function(self, ...) record("Env:Declare"); return origEnvDec(self, ...) end
HDG.Environment.Build           = function(self, ...) record("Env:Build");   return origEnvBld(self, ...) end
HDG.Middleware.Apply            = function(...)       record("MW:Apply");    return origMWApply(...) end
HDG.WidgetTypes.Flush           = function(self, ...) record("WT:Flush");    return origWTFlush(self, ...) end

-- ===== 3. Run + assert order ================================================

HDG_DB = {}
HDG.Store:LoadFromSavedVariables()

local env = _G.BootHDGREngine()

-- Expected order from wow_mock.lua's helper docstring + Init.lua:540-596.
local expected = {
    "BE:Init",        -- BlizzardEvents:OnInitialize
    "Topo",           -- Modules:Topo
    "BE:Boot",        -- BlizzardEvents:Boot(order, defs)
    "Env:Declare",    -- Environment:DeclareAll
    "Phase1",         -- Modules:Phase1
    "Env:Build",      -- Environment:Build -> env
    "MW:Apply",       -- Middleware.Apply(Store, StandardChain)
    "Phase2",         -- Modules:Phase2
    "WT:Flush",       -- WidgetTypes:Flush
}

assertEq(#sequence, #expected,
    "BootHDGREngine called " .. #sequence .. " engine steps; expected " .. #expected
    .. ". Actual: " .. table.concat(sequence, " -> "))

for i = 1, #expected do
    assertEq(sequence[i], expected[i],
        "phase order [" .. i .. "]: expected " .. expected[i] .. ", got " .. tostring(sequence[i]))
end

-- ===== 4. Env table returned ================================================

assertNotNil(env, "BootHDGREngine should return the Environment table when Environment is loaded")
assertEq(type(env), "table", "env is a table")

-- ===== 5. Idempotency =======================================================
-- A second BootHDGREngine() must not double-register modules / events.
-- This guards against test files that bootstrap, then call again to "refresh."
-- The framework's Topo / Phase1 / Phase2 internally track "already booted"
-- status. Second call should NOT re-trigger Phase1/Phase2 (those are
-- single-fire). It may re-run BE:Init/Topo (idempotent operations).

local secondRunSequence = {}
sequence = secondRunSequence  -- redirect record() target

-- Note: depending on engine implementation, second-run may be a no-op or
-- repeat the topology pass. Both are acceptable; the load-bearing assertion
-- is that the second run doesn't error.
local ok, err = pcall(_G.BootHDGREngine)
assertTrue(ok, "second BootHDGREngine() call must not error: " .. tostring(err))

-- ===== 6. Module state after boot ===========================================
-- The Modules registry must report itself as booted; new modules can no longer
-- be declared after Phase2 completes (closed-registry invariant).

local order = HDG.Modules:GetOrder()
assertTrue(type(order) == "table" and #order >= 0,
    "Modules:GetOrder() returns a table after boot")

print("test_bootstrap_lifecycle.lua passed")
