--[[ Core/Catalog.lua — catalog entry info + ownership (spike-bound names).
     REAL functions (M0): GetCatalogEntryInfoByRecordID, GetCatalogEntryInfo,
     GetDecorTotalOwnedCount, GetDecorMaxOwnedCount, CreateCatalogSearcher.

     Since the server computes numMissing per player in blueprint contents,
     Catalog is an ENRICHMENT layer (icons, categories, ownership for baked
     manifests when no live payload exists) — not the score's backbone.

     VERIFY (M2 in-game): exact field names in the entry-info struct. We probe
     defensively: any numeric field matching "own" is treated as owned count. ]]--

local _, ns = ...
local Catalog = {}
ns.Catalog = Catalog

local CC = C_HousingCatalog
local infoCache = {}   -- [recordID] = info table | false (=lookup failed)

-- VERIFIED IN GAME 2026-08-14: the call is
--   GetCatalogEntryInfoByRecordID(entryType, recordID)
-- Two arguments, not one. We were calling it with one inside a pcall, so it
-- has been failing silently since day one - which is why ownership was always
-- "unknown" and every score had to come from the server.
--
-- Returned fields that matter: quantity (owned), itemID (the real WoW item
-- behind the decor), placementCost, isAllowedIndoors/Outdoors, categoryIDs.
-- There is NO vendor or source field anywhere in C_HousingCatalog.
local ENTRY_TYPES = { 1, 3, 0, 2, 4, 5 }   -- 1 = decor, the common case first

function Catalog.InfoByRecord(recordID)
  if not recordID then return nil end
  local hit = infoCache[recordID]
  if hit ~= nil then return hit or nil end
  local fn = CC and CC.GetCatalogEntryInfoByRecordID
  if not fn then infoCache[recordID] = false; return nil end
  for _, entryType in ipairs(ENTRY_TYPES) do
    local ok, info = pcall(fn, entryType, recordID)
    if ok and type(info) == "table" then
      infoCache[recordID] = info
      return info
    end
  end
  infoCache[recordID] = false
  return nil
end

--- Owned count for a record. Returns number or nil (=unknown).
function Catalog.OwnedByRecord(recordID)
  local info = Catalog.InfoByRecord(recordID)
  if not info then return nil end
  if type(info.quantity) == "number" then return info.quantity end
  -- older field names, kept as a fallback in case the struct shifts again
  for _, key in ipairs({ "numOwned", "ownedCount", "quantityOwned", "totalOwned" }) do
    if type(info[key]) == "number" then return info[key] end
  end
  return nil
end

--- The real WoW item behind a decor piece. Gives us its icon and tooltip,
--- which is the closest thing to "where does this come from" that the API
--- offers - C_HousingCatalog has no vendor or source data at all.
function Catalog.ItemIDByRecord(recordID)
  local info = Catalog.InfoByRecord(recordID)
  return info and info.itemID or nil
end

--- Icon for a record: the catalog struct has no icon field, but the item does.
function Catalog.IconByRecord(recordID)
  local info = Catalog.InfoByRecord(recordID)
  if not info then return nil end
  if info.icon or info.iconFileID or info.iconTexture then
    return info.icon or info.iconFileID or info.iconTexture
  end
  if info.itemID and C_Item and C_Item.GetItemIconByID then
    local ok, icon = pcall(C_Item.GetItemIconByID, info.itemID)
    if ok then return icon end
  end
  return nil
end

--- Budget cost of placing one of these (from placementCost).
function Catalog.PlacementCostByRecord(recordID)
  local info = Catalog.InfoByRecord(recordID)
  return info and info.placementCost or nil
end

function Catalog.Invalidate()
  wipe(infoCache)
  ns.bus:Fire("OWNERSHIP_CHANGED")
end

-- Ownership can change when decor is bought/redeemed; the exact event name is
-- VERIFY — register candidates, harmless if they don't exist.
for _, ev in ipairs({ "HOUSING_CATALOG_UPDATED", "HOUSING_STORAGE_UPDATED" }) do
  ns.events:Register(ev, function() Catalog.Invalidate() end)
end
