-- test_boot_sequence.lua
-- ============================================================================
-- Boot-sequence end-to-end — exercises the full Init.lua boot path step by
-- step, asserting each stage's post-conditions. Catches:
--   - action meta registered out of order with ValidateExhaustiveness
--   - state factories that leave required slots nil after LoadFromSavedVariables
--   - engines that aren't ready at the moment Modules:Boot wires them
--   - first dispatch failing because something above didn't actually finish
--
-- LESSON (VFN c80fef41): a half-converted addon's boot path "looked fine"
-- because the smoke test exercised post-boot behavior. The bug was BE
-- subscribing before action meta validation finished — once we walked the
-- sequence step by step, the gap surfaced immediately.

---@diagnostic disable: undefined-global
dofile("wow_mock.lua")
dofile("../Core/VDS_Constants.lua")
dofile("../Core/VDS_SchemeConstants.lua")
dofile("../Core/VDS_Paths.lua")
dofile("../Core/VDS_Log.lua")
dofile("../Core/VDS_Store.lua")
dofile("../UI/VDS_Selectors.lua")
dofile("../UI/VDS_WidgetTypes.lua")
dofile("../UI/VDS_Theme.lua")
dofile("../UI/VDS_Palette.lua")
dofile("../UI/VDS_Components.lua")
dofile("../UI/VDS_BindingEngine.lua")
dofile("../UI/VDS_BlizzardEvents.lua")
dofile("../UI/VDS_Selectors_Progress.lua")

-- WidgetTypes:Register defers to a queue; Flush() seals + populates _registry.
-- Boot-sequence asserts the post-Flush count.
VDS.WidgetTypes:Flush()

local function step(label, fn)
    local ok, err = pcall(fn)
    if not ok then error("BOOT STEP FAILED [" .. label .. "]: " .. tostring(err), 0) end
    print("  [OK] " .. label)
end

local function assertEq(actual, expected, label)
    if actual ~= expected then
        error(label .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual), 2)
    end
end
local function assertTrue(v, label) if not v then error(label, 2) end end

print("Boot sequence:")

-- ============================================================================
-- Stage 0: TOC loaded → engine singletons exist
-- ============================================================================

step("TOC load: engine singletons exist", function()
    assertTrue(VDS.Store, "VDS.Store nil after TOC load")
    assertTrue(VDS.Selectors, "VDS.Selectors nil")
    assertTrue(VDS.WidgetTypes, "VDS.WidgetTypes nil")
    assertTrue(VDS.Theme, "VDS.Theme nil")
    assertTrue(VDS.Palette, "VDS.Palette nil")
    assertTrue(VDS.BlizzardEvents, "VDS.BlizzardEvents nil")
end)

-- ============================================================================
-- Stage 1: BlizzardEvents eager-init at file load (invariant §14)
-- The frame MUST exist before any Subscribe call, because Init.lua wants to
-- subscribe ADDON_LOADED/PLAYER_LOGIN from outside Modules:Boot.
-- ============================================================================

step("BlizzardEvents engine eager-initialized (frame exists)", function()
    assertTrue(VDS.BlizzardEvents._frame ~= nil, "BE._frame nil — would force lazy-init double-frame bug")
end)

-- ============================================================================
-- Stage 2: Action meta registration + exhaustiveness validation
-- ============================================================================

local A = VDS.Constants.ACTIONS
step("Action meta registration (from Core/VDS_ActionMeta.lua)", function()
    dofile("../Core/VDS_ActionMeta.lua")
end)

step("Action meta exhaustiveness validates", function()
    VDS.Store:ValidateActionMetaExhaustiveness()
end)

-- ============================================================================
-- Stage 3: SavedVariables load — state.account is BY REFERENCE to VDS_DB.account
-- ============================================================================

step("SavedVariables load (fresh install: VDS_DB = {})", function()
    VDS_DB = {}
    VDS.Store:LoadFromSavedVariables()
    assertTrue(VDS.Store:GetState().account ~= nil, "state.account nil after load")
    assertTrue(VDS.Store:GetState().session ~= nil, "state.session nil after load")
    assertTrue(VDS_DB.account == VDS.Store:GetState().account,
        "by-reference contract broken: VDS_DB.account is not the same table as state.account")
end)

-- ============================================================================
-- Stage 4: Selector + widget registries non-empty after file load
-- ============================================================================

step("Selectors registered (≥1)", function()
    local n = 0; for _ in pairs(VDS.Selectors._registry) do n = n + 1 end
    assertTrue(n >= 1, "No selectors registered — selector files didn't load or didn't run")
end)

step("Widget kinds registered (≥4: label/button/frame/spacer)", function()
    local n = 0; for _ in pairs(VDS.WidgetTypes._registry) do n = n + 1 end
    assertTrue(n >= 4, string.format("Expected >=4 widget kinds, got %d", n))
end)

-- ============================================================================
-- Stage 5: First dispatch round-trip (the moment a real boot would do its
-- first SESSION_IDENTITY_SET or BAG_TICK)
-- ============================================================================

step("First dispatch round-trip", function()
    VDS.Store:Dispatch({
        type = A.SESSION_IDENTITY_SET,
        payload = { name = "Tester", realm = "Frostmourne", class = "WARRIOR" },
    })
    assertEq(VDS.Store:GetState().session.identity.charKey, "Tester-Frostmourne",
        "identity not set after first dispatch")
end)

-- ============================================================================
-- Stage 6: Persistence — mutate account, verify it's mirrored in VDS_DB
-- ============================================================================

step("Persistence: account mutation visible via VDS_DB", function()
    VDS.Store:Dispatch({ type = A.MINIMAP_POSITION_SAVE, payload = { pos = 117 } })
    assertEq(VDS_DB.account.minimap.minimapPos, 117,
        "account mutation not reflected in VDS_DB (by-reference contract broken)")
end)

print("test_boot_sequence.lua passed")
