-- test_action_dispatch_smoke.lua
-- ============================================================================
-- Action-dispatch smoke for HDG — iterates Constants.ACTIONS (159 entries),
-- dispatches each through _RawDispatch with a best-effort payload, asserts
-- no error. Pairs with test_first_paint.lua to give every registered
-- surface a runtime exercise.
--
-- Empty {} payload is the default. The PAYLOADS table below overrides for
-- actions whose reducer accesses a specific field unconditionally. New
-- actions inherit the {} default — they should be authored to no-op on
-- empty payload (defensive at the producer boundary, strict inside the
-- reducer is fine).
--
-- Iteration of ACTIONS grants the action category wildcard credit in
-- test_coverage_map.lua → 100% action coverage.

local _realPrint = print
local function out(s) _realPrint(s) end

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_TableUtils.lua")
dofile("../Core/HDGR_StoreFurnishings.lua")
dofile("../Core/HDGR_Store.lua")
-- Config layer: CONFIG_SET reducer routes via Config:_GetSourceForScope;
-- PROFILE_* actions also reference HDG.Config.
dofile("../Core/HDGR_ConfigSchema.lua")
dofile("../Core/HDGR_Config.lua")
dofile("../Core/HDGR_WidgetTypes.lua")
dofile("../Core/HDGR_Expansion.lua")
dofile("../UI/HDGR_LayoutConfig.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Decor.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Acquisition.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Recipes.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Alts.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Mogul.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Styles.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Trainers.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_HouseTab.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Debug.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Config.lua")
dofile("../UI/HDGR_Theme.lua")
dofile("../UI/HDGR_Selectors.lua")
dofile("../UI/HDGR_CategoryNav.lua")
dofile("../UI/HDGR_Selectors_Status.lua")
dofile("../UI/HDGR_Selectors_Decor.lua")
dofile("../UI/HDGR_Selectors_Acquisition.lua")
dofile("../UI/HDGR_Selectors_Recipes.lua")
dofile("../UI/HDGR_Selectors_Config.lua")
dofile("../UI/HDGR_Selectors_Alts.lua")
dofile("../UI/HDGR_Selectors_Mogul.lua")
dofile("../UI/HDGR_Selectors_Styles.lua")
dofile("../UI/HDGR_Selectors_Trainers.lua")
dofile("../UI/HDGR_Selectors_House.lua")
dofile("../UI/HDGR_Selectors_Companion.lua")
dofile("../data/HDGR_DecorDB.lua")
HDGR_AllDecorDB = {}  -- stub: deprecated DB archived; test does not assert on its content
HDGR_VendorDB = {}  -- stub: deprecated DB archived; test does not assert on its content
dofile("../Modules/HDGR_StaticData.lua")
dofile("../Modules/HDGR_ShoppingCodec.lua")  -- SHOPPING_LIST_IMPORT reducer strict-calls

HDG_DB = {}
HDG.Store:LoadFromSavedVariables()
HDG.Config:Initialize()

-- ============================================================================
-- Per-action payload overrides — keyed by action VALUE (the dispatched string).
-- Only entries whose reducer accesses a field unconditionally need to live
-- here; everything else gets the {} default and relies on reducer guards.
--
-- The list grows as we discover failures: run the test, look at the
-- failure list, add a template if the reducer's access is reasonable
-- (e.g. requires charKey), OR flag it as a real bug if the reducer is
-- doing unguarded payload.X access for something legitimately optional.
-- ============================================================================

local A = HDG.Constants.ACTIONS
local PAYLOADS = {
    -- UI / config
    [A.UI_SET_PERSISTENT]  = { key = "debug", value = false },
    [A.UI_SET_TRANSIENT]   = { key = "scroll", value = 0 },
    [A.CONFIG_SET]         = { key = "debug", value = false },
    [A.NAV_TOGGLE_GROUP]   = { view = "decor" },   -- reducer keys collapsedGroups[view]

    -- Vendor + collection
    [A.VENDOR_NOTE_SET]    = { vendorID = 0, note = "" },
    [A.VENDOR_NOTE_CLEAR]  = { vendorID = 0 },
    [A.COLLECTION_ITEM_LEARNED]   = { itemID = 0 },
    [A.COLLECTION_ITEM_REMOVED]   = { itemID = 0 },
    [A.COLLECTION_BULK_LOAD]      = { entries = {}, owned = {}, live = {}, swept_at = 0, clientVer = 0 },

    -- Characters
    [A.CHARACTER_DELETED]            = { charKey = "Tester-Frostmourne" },
    [A.CHARACTER_HIDDEN]             = { charKey = "Tester-Frostmourne", hidden = true },
    [A.CHARACTER_PROFESSION_UPDATED] = { charKey = "Tester-Frostmourne", professions = {} },

    -- Session identity (reducer concatenates name .. "-" .. realm)
    [A.SESSION_IDENTITY_SET]         = { name = "Tester", realm = "Frostmourne", class = "WARRIOR", classFile = "WARRIOR" },

    -- HouseTab layout actions — all require widgetID
    [A.HOUSETAB_TOGGLE_WIDGET]       = { widgetID = "test" },
    [A.HOUSETAB_SET_ORDER]           = { widgetID = "test", order = 0 },
    [A.HOUSETAB_SET_WIDTH]           = { widgetID = "test", width = 100 },
    [A.HOUSETAB_RESIZE_WIDGET]       = { widgetID = "test", height = 100 },

    -- Recipes section/filter
    [A.RECIPES_SET_SECTION_EXPAND]   = { key = "test", expanded = true },
    [A.RECIPES_TOGGLE_FILTER]        = { filter = "test" },
    -- expansion + profession become SET keys in account.ui.recipes, so
    -- table[nil] errors without a value (same shape as SHOPPING_LIST_DELETE).
    [A.RECIPES_TOGGLE_EXPANSION]     = { expansion = "The War Within" },
    [A.RECIPES_TOGGLE_PROFESSION]    = { profession = "Alchemy" },
    -- Acquire advanced-filter multi-select toggles: set keys, so table[nil] errors
    -- without a value (same shape as RECIPES_TOGGLE_EXPANSION).
    [A.ACQ_TOGGLE_EXPANSION]         = { expansion = "Midnight" },
    [A.ACQ_TOGGLE_ZONE]              = { zone = "Stormwind City" },
    [A.ACQ_TOGGLE_REP]               = { rep = "The Singularity" },
    [A.ACQ_TOGGLE_SOURCE]            = { source = "vendor" },
    [A.ACQ_TOGGLE_FACTION]           = { faction = "Alliance" },

    -- Prices scan
    [A.PRICES_DIRECT_SCAN_BATCH]     = { prices = {} },
    [A.PRICES_DIRECT_SCAN_COMPLETED] = { neededItems = {}, now = 0 },

    -- Styles bulk observe
    [A.STYLES_PLACED_DECOR_OBSERVED_BATCH] = { entries = {}, bulkFill = true },

    -- Shopping list -- id-keyed list actions need a list to exist for the
    -- reducer to mutate (no-op on nonexistent). DELETE specifically fails
    -- on `table[nil] = nil` without an id payload, hence templates here.
    [A.SHOPPING_LIST_DELETE]    = { id = "L1" },
    [A.SHOPPING_LIST_RENAME]    = { id = "L1", name = "x" },
    [A.SHOPPING_LIST_DUPLICATE] = { id = "L1" },
    [A.SHOPPING_LIST_ACTIVATE]  = { id = "L1" },
    [A.SHOPPING_LIST_CLEAR]     = { id = "L1" },
    [A.SHOPPING_LIST_SET_META]  = { id = "L1", key = "source", value = "x" },
    [A.SHOPPING_LIST_IMPORT]    = { encoded = "garbage" },   -- Decode returns nil -> no-op
    [A.SHOPPING_ITEM_ADD]       = { itemID = 0, qty = 1 },
    [A.SHOPPING_ITEM_REMOVE]    = { itemID = 0 },
    [A.SHOPPING_ITEM_SET_QTY]   = { itemID = 0, qty = 1 },
    -- Lumber Tracker (Phase 4 actions). lumberID becomes a table key in
    -- the per-char sessions map for SESSION_START; without a template the
    -- reducer would `table[nil] = ...` and error.
    [A.LUMBER_HARVESTED]        = { lumberID = 245586, qty = 1, x = 0, y = 0, mapID = 0, timestamp = 0 },
    [A.LUMBER_SESSION_START]    = { lumberID = 245586, timestamp = 0, startCount = 0 },
    [A.LUMBER_SESSION_END]      = { timestamp = 0 },
    [A.LUMBER_BLIP_GC]          = { now = 0 },
    [A.LUMBER_WINDOW_POSITION_SET] = { x = 0, y = 0 },
    [A.LUMBER_RADAR_SCALE_SET]  = { scale = 1.0 },
    -- Catalog lifecycle — reducers do strict reads; all payload fields required
    [A.CATALOG_LOAD_COMPLETED]  = { loadedAt = 0, itemCount = 0, vendorCount = 0, generation = 0 },
    [A.CATALOG_VINTAGE_UPDATE]  = { ids = { 1 }, build = 120005, label = "12.0.5", isSeed = true },
    [A.UI_SET_VIEW]             = { view = "catalog" },
}

-- ============================================================================
-- Iterate every action, dispatch with template-or-{} payload.
-- ============================================================================

local actionList = {}
for name, value in pairs(HDG.Constants.ACTIONS) do
    actionList[#actionList + 1] = { name = name, value = value }
end
table.sort(actionList, function(a, b) return a.name < b.name end)

local failures, successes = {}, 0
for _, a in ipairs(actionList) do
    local payload = PAYLOADS[a.value] or {}
    local ok, err = pcall(HDG.Store._RawDispatch, HDG.Store, {
        type = a.value, payload = payload,
    })
    if ok then
        successes = successes + 1
    else
        failures[#failures + 1] = string.format("  %s: %s", a.name, tostring(err))
    end
end

out(string.format("HDG action dispatch: %d/%d actions reduced cleanly",
    successes, #actionList))

if #failures > 0 then
    out("FAILURES (first 30):")
    for i = 1, math.min(#failures, 30) do out(failures[i]) end
    if #failures > 30 then out(string.format("  ... and %d more", #failures - 30)) end
    out("")
    out("Add a payload template above for each, OR add a reducer guard if the")
    out("missing field is genuinely optional.")
    error(string.format("test_action_dispatch_smoke: %d actions failed", #failures), 0)
end

out("test_action_dispatch_smoke.lua passed")
