-- test_observer_row_schema.lua
-- Verifies enriched row schema: identity, catalog scalars, parsed sourceText
-- (vendors[]/quest/achievement/category/factionGate) all populate correctly.

dofile("wow_mock.lua")
dofile("../Core/HDGR_Constants.lua")
dofile("../Core/HDGR_Actions.lua")
dofile("../Core/HDGR_Resolvers.lua")
dofile("../Core/HDGR_Format.lua")
dofile("../Core/HDGR_StoreFurnishings.lua")
dofile("../Core/HDGR_Store.lua")

-- Stub HDG.Modules so the Declare() call at the bottom of the observer file
-- doesn't fail (real Modules engine is exercised in-game).
HDG.Modules = HDG.Modules or {}
HDG.Modules._decls = {}
function HDG.Modules:Declare(def)
    self._decls[def.name] = def
end

-- Stub HDG.Selectors so Store:Dispatch (which calls InvalidateMemos) works
-- without loading the full Selectors engine. Task 2.7 tests need Dispatch
-- for CATALOG_LOAD_COMPLETED; the real engine is exercised in reactivity tests.
HDG.Selectors = HDG.Selectors or {}
function HDG.Selectors:InvalidateMemos(_) end

-- Stub HDG.StaticData.CatalogOverrides. Observer's BuildRow strict-calls
-- :Get(itemID) on it (Phase 5 Task 5.2 removed the nil-guard). Returning
-- nil from the stub is the "no override for this item" path the test wants.
HDG.StaticData = HDG.StaticData or {}
HDG.StaticData.CatalogOverrides = HDG.StaticData.CatalogOverrides or {
    Get = function(self, itemID) return nil end,
}

-- Stub C_HousingCatalog category-info APIs. BuildRow consolidation (2026-05-26)
-- now resolves categoryName / subcategoryName at row-build time via
-- resolveCategoryName / resolveSubcategoryName. Strict reads mean the
-- observer hard-errors if the API isn't present in the test env.
_G.C_HousingCatalog = _G.C_HousingCatalog or {}
_G.C_HousingCatalog.GetCatalogCategoryInfo = _G.C_HousingCatalog.GetCatalogCategoryInfo
    or function(catID) return { name = "TestCategory_" .. tostring(catID) } end
_G.C_HousingCatalog.GetCatalogSubcategoryInfo = _G.C_HousingCatalog.GetCatalogSubcategoryInfo
    or function(subID) return { name = "TestSubcategory_" .. tostring(subID) } end

dofile("../Modules/HDGR_HousingCatalogObserver.lua")

HDG.Store:LoadFromSavedVariables()

-- Fixture: build a minimal info table and pass through observer:BuildRow
local fakeInfo = {
    recordID = 12345, itemID = 67890, name = "Test Decor",
    iconTexture = 999, quality = 2, size = 67, placementCost = 3,
    isAllowedIndoors = true, isAllowedOutdoors = true, canCustomize = false,
    categoryIDs = { 1, 5 }, subcategoryIDs = { 12 },
    dataTagsByID = { [110] = "Midnight", [68] = "Large" },
    dyeSlots = {},
    quantity = 1, numPlaced = 0, totalNumStored = 1, totalNumPlaced = 0,
    firstAcquisitionBonus = 5,
    sourceText = "|cFFFFD200Vendor: |rOm'sirik|n|cFFFFD200Zone: |rK'aresh|n|cFFFFD200Cost: |r800",
}

local row = HDG.HousingCatalogObserver:BuildRow(fakeInfo)

assert(row.itemID == 67890, "itemID")
assert(row.decorID == 12345, "decorID = recordID")
assert(row.name == "Test Decor", "name")
assert(row.iconTexture == 999, "iconTexture")
assert(row.categoryID == 1, "categoryID extracted from categoryIDs[1]")
assert(row.subcategoryID == 12, "subcategoryID extracted")
assert(row.dataTagsByID[110] == "Midnight", "dataTagsByID preserved verbatim")
-- Consolidated 2026-05-26: row.collected -> row.isOwned (single canonical
-- predicate; includes remainingRedeemable per HDG IsDecorCollected semantics).
assert(row.isOwned == true, "isOwned derived from quantity+remainingRedeemable+numPlaced > 0")
assert(HDG.HousingCatalogObserver:IsOwned(row) == true, "R:IsOwned(row) matches stamped row.isOwned")
assert(#row.vendors == 1, "parsed one vendor from sourceText")
assert(row.vendors[1].name == "Om'sirik", "vendor name parsed")
assert(row.vendors[1].zone == "K'aresh", "vendor zone parsed")
assert(row.vendors[1].cost == "800", "vendor cost parsed")
assert(row.factionGate == nil, "factionGate nil when no Faction: line")

print("test_observer_row_schema: BuildRow PASS")

-- ===== Reverse index population via _PopulateReverseIndexes =================

local fakeRow1 = HDG.HousingCatalogObserver:BuildRow(fakeInfo)
local fakeRow2 = HDG.HousingCatalogObserver:BuildRow({
    recordID = 99, itemID = 100, name = "Other Decor",
    iconTexture = 1, quality = 2, size = 67, placementCost = 1,
    isAllowedIndoors = true, isAllowedOutdoors = true, canCustomize = false,
    categoryIDs = {1}, subcategoryIDs = {2},
    dataTagsByID = { [110] = "Midnight" }, dyeSlots = {},
    quantity = 0, numPlaced = 0, totalNumStored = 0, totalNumPlaced = 0,
    firstAcquisitionBonus = 0,
    sourceText = "|cFFFFD200Vendor: |rAnother NPC|n|cFFFFD200Zone: |rOther Zone|n",
})

-- 2026-05-27: _PopulateReverseIndexes deleted; reverse-index build is now
-- inline in _OnSearcherResults (single pass over searcher items). The
-- function-level invariant we still want to assert: a row carries the
-- vendor info that downstream byVendor builds depend on. The full sweep
-- semantics are covered by the in-game smoke test now.
assert(fakeRow1.vendors and fakeRow1.vendors[1].name == "Om'sirik",
    "fakeRow1 carries vendor info for byVendor build")
assert(fakeRow2.vendors and fakeRow2.vendors[1].name == "Another NPC",
    "fakeRow2 carries vendor info for byVendor build")

-- Manually populate byItemID + byVendor for the remaining API surface tests below.
-- (Production: this happens in _OnSearcherResults atomic-assign in one pass.)
HDG.HousingCatalogObserver.byItemID = {
    [fakeRow1.itemID] = fakeRow1,
    [fakeRow2.itemID] = fakeRow2,
}
HDG.HousingCatalogObserver.byDecorID = {
    [fakeRow1.decorID] = fakeRow1,
    [fakeRow2.decorID] = fakeRow2,
}
-- byVendor is keyed by composite "<name>::<zone>" (2026-05-27) -- catalog
-- ships ~9 distinct vendors sharing a display name across zones.
HDG.HousingCatalogObserver.byVendor = {
    ["Om'sirik::K'aresh"]       = { name = "Om'sirik",    items = { fakeRow1.itemID }, zone = "K'aresh",    _seenItems = { [fakeRow1.itemID] = true } },
    ["Another NPC::Other Zone"] = { name = "Another NPC", items = { fakeRow2.itemID }, zone = "Other Zone", _seenItems = { [fakeRow2.itemID] = true } },
}
HDG.HousingCatalogObserver.allVendorNames = { "Another NPC", "Om'sirik" }

print("test_observer_row_schema: reverse indexes PASS")

-- ===== Public API methods (Task 2.7) =========================================
local A = HDG.Constants.ACTIONS

-- IsReady before/after CATALOG_LOAD_COMPLETED
assert(HDG.HousingCatalogObserver:IsReady() == false, "IsReady false until sweep completes")
HDG.Store:Dispatch({
    type = A.CATALOG_LOAD_COMPLETED,
    payload = { loadedAt=0, itemCount=2, vendorCount=2, generation=1 },
})
assert(HDG.HousingCatalogObserver:IsReady() == true, "IsReady after CATALOG_LOAD_COMPLETED")

-- GetRow, GetIcon
assert(HDG.HousingCatalogObserver:GetRow(67890).name == "Test Decor", "GetRow returns row")
assert(HDG.HousingCatalogObserver:GetIcon(67890) == 999, "GetIcon returns iconTexture")

-- GetExpansionForItem -- depends on R.tagIDToGroup being populated.
-- _BuildTagIDIndex requires Blizzard API + so won't run in test environment.
-- Manually populate the index for this test:
HDG.HousingCatalogObserver.tagIDToGroup[110] = "Expansion"
assert(HDG.HousingCatalogObserver:GetExpansionForItem(67890) == "Midnight",
       "GetExpansionForItem joins dataTagsByID x Expansion group")

-- GetVendorsForItem
local v = HDG.HousingCatalogObserver:GetVendorsForItem(67890)
assert(v and #v == 1, "GetVendorsForItem")

-- GetCategoryForItem -- nil for the fixture (no Category: line in sourceText)
assert(HDG.HousingCatalogObserver:GetCategoryForItem(67890) == nil,
       "GetCategoryForItem nil for non-Category sourceText")

-- GetBindTypeForItem -- "BoP" for non-Professions
assert(HDG.HousingCatalogObserver:GetBindTypeForItem(67890) == "BoP",
       "GetBindTypeForItem BoP for non-Professions category")

-- GetItemsByVendor + GetAllVendorNames
local vRow = HDG.HousingCatalogObserver:GetItemsByVendor("Om'sirik", "K'aresh")
assert(vRow and vRow.items and #vRow.items >= 1, "GetItemsByVendor")

local names = HDG.HousingCatalogObserver:GetAllVendorNames()
assert(#names >= 1, "GetAllVendorNames")

-- IterateRows + GetItemCount
local count = 0
HDG.HousingCatalogObserver:IterateRows(function() count = count + 1 end)
assert(count >= 1, "IterateRows")
assert(HDG.HousingCatalogObserver:GetItemCount() == count,
       "GetItemCount matches iteration count")

print("test_observer_row_schema: public API PASS")
