-- Housing Decor Guide - Acquisition Data & Filtering
-- Data caches, filter state, and filtering logic. No UI frames.

local addonName, addon = ...

HDG_AcqData = HDG_AcqData or {}

local _clientInterfaceVersion = select(4, (type(Build) == "table" and Build.GetBuildInfo or GetBuildInfo)())

-- Zone/expansion data from HDG_ExpansionData (SSoT)
local ZONE_MAP_IDS = HDG_ExpansionData.ZONE_MAP_IDS
local GetExpansionForMapID = HDG_ExpansionData.GetExpansionForMapID
local NormalizeToDisplayName = HDG_ExpansionData.NormalizeToDisplayName

-- ============================================================================
-- DIRECT DB ACCESSORS (replace redundant cache tables)
-- ============================================================================

-- sourceInfo replacement: reads HDG_AllDecorDB directly
-- AllDecorDB: [itemID] = {[1]=decorID, [2]=sourceType, [3]=sourceName, [4]=sourceDetail}
local function GetSourceInfo(itemID)
    return HDG_AllDecorDB and HDG_AllDecorDB[itemID]
end

local function GetSourceType(itemID)
    local e = HDG_AllDecorDB and HDG_AllDecorDB[itemID]
    return e and e[2] or 0
end

local function GetAltSourceType(itemID)
    local e = HDG_AllDecorDB and HDG_AllDecorDB[itemID]
    local alt = e and e.alt
    return alt and alt[1] or 0
end

-- vendorInfo replacement: reads HDG_VendorDB directly
-- VendorDB: [npcID] = {[1]=name, [2]=zone, [3]=mapID, [4]=x, [5]=y, [6]=faction}
local function GetVendorName(npcID)
    local v = HDG_VendorDB and HDG_VendorDB[npcID]
    return v and v[1] or ("NPC #" .. npcID)
end

local function GetVendorZone(npcID)
    local v = HDG_VendorDB and HDG_VendorDB[npcID]
    return v and v[2] or "Unknown"
end

local function GetVendorMapID(npcID)
    local v = HDG_VendorDB and HDG_VendorDB[npcID]
    return v and v[3] or 0
end

-- Expose accessors for other modules (AcqRows, AcquisitionTab)
HDG_AcqData.GetSourceInfo = GetSourceInfo
HDG_AcqData.GetVendorName = GetVendorName
HDG_AcqData.GetVendorZone = GetVendorZone
HDG_AcqData.GetVendorMapID = GetVendorMapID

-- ============================================================================
-- FILTER STATE (single table — SSoT for all filter values)
-- ============================================================================

HDG_AcqData.filters = {
    search = "",
    zone = "All",
    faction = "All",
    source = "All",
    expansion = "All",
    uncollectedOnly = false,
    dyeableOnly = false,
    releasedOnly = true,
}

-- ============================================================================
-- DATA CACHES (built once per Show, wiped on rebuild)
-- ============================================================================

HDG_AcqData.vendorItems = {}        -- [npcID] = {itemID, ...}
HDG_AcqData.vendorExpansion = {}    -- [npcID] = expansion string (computed from mapID)
HDG_AcqData.itemExpansion = {}      -- [itemID] = expansion string (for non-vendor items)
HDG_AcqData.allItems = {}           -- merged deduped acquirable itemIDs
HDG_AcqData.craftableByItem = {}    -- [itemID] = spellID
HDG_AcqData.recipeByItem = {}       -- [itemID] = DecorDB recipe table
HDG_AcqData.uniqueZones = {}
HDG_AcqData.uniqueFactions = {}
HDG_AcqData.uniqueExpansions = {}

-- Filtered results (rebuilt per filter change)
HDG_AcqData.filteredItems = {}      -- itemIDs passing filters (By Item view)
HDG_AcqData.filteredUncollectedCount = 0 -- uncollected count (computed during rebuild)
HDG_AcqData.filteredVendors = {}    -- npcIDs with matching items (By Vendor view)
HDG_AcqData.vendorCounts = {}       -- [npcID] = {total=N, collected=N}
HDG_AcqData.vendorFilteredItems = {} -- [npcID] = {itemIDs passing filters} (cached per rebuild)

-- Internal: acquirable source type set (built from CONFIG)
local acquirableTypeSet = {}

-- ============================================================================
-- SOURCE TYPE HELPERS (deduplicated from 2 locations)
-- ============================================================================

-- All three lookups now read from CONFIG.SOURCE_TYPES (single source of truth
-- in HDG_Config.lua). No local copies — adding a new type is one-line.

function HDG_AcqData.GetSourceTypeLabel(srcType, CONFIG)
    local info = CONFIG and CONFIG.SOURCE_TYPES and CONFIG.SOURCE_TYPES[srcType]
    return (info and info.display) or "Source"
end

function HDG_AcqData.GetSourceTypeAtlas(srcType, CONFIG)
    local info = CONFIG and CONFIG.SOURCE_TYPES and CONFIG.SOURCE_TYPES[srcType]
    return info and info.atlas
end

function HDG_AcqData.IsAcquirableType(srcType)
    return acquirableTypeSet[srcType] or false
end

-- ============================================================================
-- BUILD CACHES (called once per Show)
-- ============================================================================

function HDG_AcqData.BuildCaches(CONFIG)
    wipe(HDG_AcqData.vendorItems)
    wipe(HDG_AcqData.vendorExpansion)
    wipe(HDG_AcqData.itemExpansion)
    wipe(HDG_AcqData.craftableByItem)
    wipe(HDG_AcqData.recipeByItem)
    wipe(HDG_AcqData.allItems)
    wipe(HDG_AcqData.uniqueZones)
    wipe(HDG_AcqData.uniqueFactions)
    wipe(HDG_AcqData.uniqueExpansions)
    wipe(HDG_AcqData.vendorCounts)

    -- Build craftableByItem and recipeByItem from DecorDB. HDG_DecorDB is
    -- keyed by a legacy decor record id, NOT the recipe spellID — read the
    -- spellID from `recipe.spellID`. The downstream `craftableByItem` cache
    -- is documented as `[itemID] = spellID`.
    if HDG_DecorDB then
        for dbKey, recipe in pairs(HDG_DecorDB) do
            if recipe.itemID and recipe.reagents and next(recipe.reagents) then
                HDG_AcqData.craftableByItem[recipe.itemID] = recipe.spellID or dbKey
                HDG_AcqData.recipeByItem[recipe.itemID] = recipe
            end
        end
    end

    -- Build vendor data from VendorDB (vendor-keyed format)
    local zoneSet = {}
    local factionSet = {}
    local expansionSet = {}
    local itemSet = {} -- [itemID] = true (dedup)
    local vendorOnlyItems = {}

    if HDG_VendorDB then
        for npcID, vEntry in pairs(HDG_VendorDB) do
            if vEntry.items then
                local zone = vEntry[2] or "Unknown"
                local mapID = vEntry[3] or 0
                local exp = vEntry.exp or GetExpansionForMapID(mapID)
                HDG_AcqData.vendorExpansion[npcID] = exp

                if zone ~= "" and not zoneSet[zone] then
                    zoneSet[zone] = true
                    table.insert(HDG_AcqData.uniqueZones, zone)
                end

                if exp and not expansionSet[exp] then
                    expansionSet[exp] = true
                    table.insert(HDG_AcqData.uniqueExpansions, exp)
                end

                HDG_AcqData.vendorItems[npcID] = {}
                for itemID, itemEntry in pairs(vEntry.items) do
                    table.insert(HDG_AcqData.vendorItems[npcID], itemID)

                    if not HDG_AcqData.craftableByItem[itemID] and not itemSet[itemID] then
                        itemSet[itemID] = true
                        table.insert(vendorOnlyItems, itemID)
                    end

                    local factionName = itemEntry[4] or ""
                    if factionName ~= "" and not factionSet[factionName] then
                        factionSet[factionName] = true
                        table.insert(HDG_AcqData.uniqueFactions, factionName)
                    end
                end
            end
        end
    end

    table.sort(HDG_AcqData.uniqueZones)
    table.sort(HDG_AcqData.uniqueFactions)

    -- Build acquirable type set from config
    wipe(acquirableTypeSet)
    if CONFIG.ACQUIRABLE_SOURCE_TYPES then
        for _, t in ipairs(CONFIG.ACQUIRABLE_SOURCE_TYPES) do
            acquirableTypeSet[t] = true
        end
    end

    -- Merge AllDecorDB: add non-vendor acquirable items (read AllDecorDB directly, no sourceInfo copy)
    local sourceOnlyItems = {}
    local sourceOnlySet = {}

    if HDG_AllDecorDB then
        for aItemID, entry in pairs(HDG_AllDecorDB) do
            local srcType = entry[2]
            if acquirableTypeSet[srcType] and not itemSet[aItemID] then
                table.insert(sourceOnlyItems, aItemID)
                sourceOnlySet[aItemID] = true
            end
        end
    end

    -- Add craftable items not already covered by vendor or AllDecorDB paths
    local craftOnlyItems = {}
    for cItemID, _ in pairs(HDG_AcqData.craftableByItem) do
        if not itemSet[cItemID] and not sourceOnlySet[cItemID] then
            table.insert(craftOnlyItems, cItemID)
        end
    end

    -- Build itemExpansion for non-vendor items (craftable + AllDecorDB)
    local function resolveItemExpansion(itemID)
        -- Live catalog tags first (Blizzard's source of truth — handles
        -- mistagged AllDecorDB entries like the Vanquisher Trophies).
        local catalogRow = HDG_Data and HDG_Data.GetCatalogByItem and HDG_Data.GetCatalogByItem(itemID)
        if catalogRow and catalogRow.tags and HDG_ExpansionData
           and HDG_ExpansionData.GetExpansionFromCatalogTags then
            local tagExp = HDG_ExpansionData.GetExpansionFromCatalogTags(catalogRow.tags)
            if tagExp then return tagExp end
        end
        -- Craftable: use recipe expansion (normalized from "Classic Alchemy" → "Classic")
        local recipe = HDG_AcqData.recipeByItem[itemID]
        if recipe and recipe.expansion then
            local exp = NormalizeToDisplayName(recipe.expansion)
            if exp and exp ~= "Unknown" then return exp end
        end
        -- AllDecorDB: try exp field first, then zone lookup from sourceDetail
        local entry = HDG_AllDecorDB and HDG_AllDecorDB[itemID]
        if entry then
            if entry.exp then return entry.exp end
            local detail = entry[4]
            if detail then
                local detailMapID = ZONE_MAP_IDS[detail]
                if detailMapID then
                    local exp = GetExpansionForMapID(detailMapID)
                    if exp and exp ~= "Other" then return exp end
                end
            end
        end
        return nil
    end

    for _, itemID in ipairs(sourceOnlyItems) do
        local exp = resolveItemExpansion(itemID)
        if exp then
            HDG_AcqData.itemExpansion[itemID] = exp
            if not expansionSet[exp] then
                expansionSet[exp] = true
                table.insert(HDG_AcqData.uniqueExpansions, exp)
            end
        end
    end
    for _, itemID in ipairs(craftOnlyItems) do
        local exp = resolveItemExpansion(itemID)
        if exp then
            HDG_AcqData.itemExpansion[itemID] = exp
            if not expansionSet[exp] then
                expansionSet[exp] = true
                table.insert(HDG_AcqData.uniqueExpansions, exp)
            end
        end
    end

    -- Re-sort expansions after adding non-vendor entries
    table.sort(HDG_AcqData.uniqueExpansions, function(a, b)
        return HDG_ExpansionData.GetSortOrder(a) < HDG_ExpansionData.GetSortOrder(b)
    end)

    -- Build merged list: vendor items + source-only items + craft-only items
    -- Pre-filter DNT/AUTOGEN items so ItemPassesFilters doesn't need to check per call
    wipe(HDG_AcqData.allItems)
    local function addIfValid(id)
        local name = HDG_Data.GetCachedItemName(id) or ""
        if name:find("[DNT]", 1, true) or name:find("[AUTOGEN]", 1, true) then return end
        table.insert(HDG_AcqData.allItems, id)
    end
    for _, id in ipairs(vendorOnlyItems) do addIfValid(id) end
    for _, id in ipairs(sourceOnlyItems) do addIfValid(id) end
    for _, id in ipairs(craftOnlyItems) do addIfValid(id) end

    -- Count achievement-source items and how many are collected
    local achItemCount, achCollectedCount = 0, 0
    local ST = CONFIG.SOURCE_TYPE
    if HDG_AllDecorDB and ST then
        for itemID, entry in pairs(HDG_AllDecorDB) do
            if entry[2] == ST.ACHIEVEMENT then
                achItemCount = achItemCount + 1
                if HDG_Data and HDG_Data.IsItemCollected and HDG_Data.IsItemCollected(itemID) then
                    achCollectedCount = achCollectedCount + 1
                end
            end
        end
    end
    HDG_AcqData.achievementItemCount = achItemCount
    HDG_AcqData.achievementCollectedCount = achCollectedCount
end

-- ============================================================================
-- FILTERING
-- ============================================================================

-- Source filter dispatch table (built lazily after CONFIG available)
local SOURCE_MATCHERS

local function BuildSourceMatchers(CONFIG)
    if SOURCE_MATCHERS then return end
    local ST = CONFIG.SOURCE_TYPE
    SOURCE_MATCHERS = {
        Vendor = function(itemID)
            local npcIDs = HDG_VendorByItem and HDG_VendorByItem[itemID]
            return npcIDs and #npcIDs > 0
        end,
        Achievement = function(itemID)
            return GetSourceType(itemID) == ST.ACHIEVEMENT or GetAltSourceType(itemID) == ST.ACHIEVEMENT
        end,
        Quest = function(itemID)
            local t = GetSourceType(itemID)
            local a = GetAltSourceType(itemID)
            return t == ST.QUEST or t == ST.WORLD_QUEST or a == ST.QUEST or a == ST.WORLD_QUEST
        end,
        Drop = function(itemID)
            return GetSourceType(itemID) == ST.DROP or GetAltSourceType(itemID) == ST.DROP
        end,
        Treasure = function(itemID)
            return GetSourceType(itemID) == ST.TREASURE or GetAltSourceType(itemID) == ST.TREASURE
        end,
        Promotional = function(itemID)
            return GetSourceType(itemID) == ST.PROMOTION or GetAltSourceType(itemID) == ST.PROMOTION
        end,
        ["In-Game Shop"] = function(itemID)
            return GetSourceType(itemID) == ST.SHOP or GetAltSourceType(itemID) == ST.SHOP
        end,
        Crafted = function(itemID)
            return HDG_AcqData.craftableByItem[itemID] ~= nil
        end,
        Reputation = function(itemID)
            local npcIDs = HDG_VendorByItem and HDG_VendorByItem[itemID]
            if not npcIDs or #npcIDs == 0 then return false end
            for _, npcID in ipairs(npcIDs) do
                local vendor = HDG_VendorDB[npcID]
                if vendor and vendor.items and vendor.items[itemID] then
                    local itemData = vendor.items[itemID]
                    if itemData[2] and itemData[2] > 0 then return true end
                end
            end
            return false
        end,
        Endeavor = function(itemID)
            local npcIDs = HDG_VendorByItem and HDG_VendorByItem[itemID]
            if not npcIDs or #npcIDs == 0 then return false end
            for _, npcID in ipairs(npcIDs) do
                local ie = HDG_VendorDB[npcID] and HDG_VendorDB[npcID].items and HDG_VendorDB[npcID].items[itemID]
                local costs = ie and ie[5]
                if costs and type(costs) == "table" then
                    for _, t in ipairs(costs) do
                        if t[1] == "c" and t[2] == CONFIG.ACQUISITION_TAB.ENDEAVOR_CURRENCY_ID then
                            return true
                        end
                    end
                end
            end
            return false
        end,
        ["Gold Only"] = function(itemID)
            local dominated = {[ST.ACHIEVEMENT]=1,[ST.QUEST]=1,[ST.WORLD_QUEST]=1,[ST.PROMOTION]=1}
            if dominated[GetSourceType(itemID)] or dominated[GetAltSourceType(itemID)] then
                return false
            end
            local npcIDs = HDG_VendorByItem and HDG_VendorByItem[itemID]
            if not npcIDs or #npcIDs == 0 then return false end
            for _, npcID in ipairs(npcIDs) do
                local vendor = HDG_VendorDB[npcID]
                if not vendor.race then
                    local ie = vendor.items and vendor.items[itemID]
                    if ie and ie[1] > 0 and (not ie[2] or ie[2] == 0) and not ie[5] then
                        return true
                    end
                end
            end
            return false
        end,
    }
end

function HDG_AcqData.ItemPassesFilters(itemID, CONFIG)
    local filters = HDG_AcqData.filters

    -- Uncollected filter
    if filters.uncollectedOnly and HDG_Data.IsItemCollected(itemID) then return false end

    -- Hoist shared lookups (used by releasedOnly, source filter, and vendor checks)
    local allDecorEntry = HDG_AllDecorDB and HDG_AllDecorDB[itemID]
    local npcIDs = HDG_VendorByItem and HDG_VendorByItem[itemID]
    local hasVendor = npcIDs and #npcIDs > 0
    local srcType = allDecorEntry and allDecorEntry[2] or 0
    local hasSource = srcType ~= 0 and acquirableTypeSet[srcType]
    local isCrafted = HDG_AcqData.craftableByItem[itemID] ~= nil

    -- Released-only filter: hide unreleased/datamined items
    if filters.releasedOnly then
        -- ver is authoritative when present
        if allDecorEntry and allDecorEntry.ver then
            if allDecorEntry.ver > _clientInterfaceVersion then return false end
        else
            -- Legacy heuristics for items without ver
            local itemName = HDG_Data.GetCachedItemName(itemID) or ""
            if itemName == "Unreleased" or itemName == "Loading..." then return false end
            if HDG_AllDecorDB and not allDecorEntry then return false end
            if allDecorEntry and allDecorEntry[5] and allDecorEntry[5] > time() then return false end
        end
        -- Zero-cost vendors with no other source
        if hasVendor then
            local allZeroCost = true
            for _, npc in ipairs(npcIDs) do
                local ve = HDG_VendorDB[npc]
                local ie = ve and ve.items and ve.items[itemID]
                if ie and (ie[1] ~= 0 or ie[2] ~= 0 or ie[5]) then allZeroCost = false; break end
            end
            if allZeroCost and not hasSource and not isCrafted then return false end
        end
    end

    -- Dyeable filter
    if filters.dyeableOnly then
        local adEntry = HDG_AllDecorDB and HDG_AllDecorDB[itemID]
        if not (adEntry and adEntry.canCustomize) then return false end
    end

    -- Search filter (uses pre-lowercased cache; search is already lowered by EditBox handler)
    if filters.search ~= "" then
        local nameLower = HDG_Data.GetCachedItemNameLower(itemID)
        if not nameLower:find(filters.search, 1, true) then return false end
    end

    -- Source filter
    if filters.source ~= "All" then
        BuildSourceMatchers(CONFIG)
        local matcher = SOURCE_MATCHERS[filters.source]
        if matcher and not matcher(itemID) then return false end
    end

    -- Must have vendor, acquirable source, or be craftable
    if not hasVendor and not hasSource and not isCrafted then return false end

    -- Non-vendor items: no rep data, so exclude when rep filter is active
    if not hasVendor then
        if filters.faction ~= "All" then return false end
        if filters.zone ~= "All" then return false end
        if filters.expansion ~= "All" then
            local exp = HDG_AcqData.itemExpansion[itemID]
            if not exp or exp ~= filters.expansion then return false end
        end
        return true
    end

    -- Vendor-specific filters (single merged loop — saves up to 2N iterations)
    local needExp = filters.expansion ~= "All"
    local needZone = filters.zone ~= "All"
    local needFaction = filters.faction ~= "All"
    if needExp or needZone or needFaction then
        local expOk, zoneOk, factionOk = not needExp, not needZone, not needFaction
        for _, npc in ipairs(npcIDs) do
            local ve = HDG_VendorDB[npc]
            if not expOk and HDG_AcqData.vendorExpansion[npc] == filters.expansion then expOk = true end
            if not zoneOk and ve and (ve[2] or "Unknown") == filters.zone then zoneOk = true end
            if not factionOk then
                local ie = ve and ve.items and ve.items[itemID]
                if ie and (ie[4] or "") == filters.faction then factionOk = true end
            end
            if expOk and zoneOk and factionOk then break end
        end
        if not expOk or not zoneOk or not factionOk then return false end
    end

    return true
end

-- ============================================================================
-- REBUILD FILTERED LISTS
-- ============================================================================

-- Hoisted sort comparators (avoid closure allocation per sort call)
local function sortByItemName(a, b)
    return (HDG_Data.GetCachedItemName(a) or "") < (HDG_Data.GetCachedItemName(b) or "")
end

local function sortByVendorName(a, b)
    return GetVendorName(a) < GetVendorName(b)
end

function HDG_AcqData.RebuildFilteredItems(CONFIG)
    wipe(HDG_AcqData.filteredItems)
    local uncollected = 0
    for _, itemID in ipairs(HDG_AcqData.allItems) do
        if HDG_AcqData.ItemPassesFilters(itemID, CONFIG) then
            table.insert(HDG_AcqData.filteredItems, itemID)
            if not HDG_Data.IsItemCollected(itemID) then uncollected = uncollected + 1 end
        end
    end
    HDG_AcqData.filteredUncollectedCount = uncollected
    table.sort(HDG_AcqData.filteredItems, sortByItemName)
end

function HDG_AcqData.RebuildFilteredVendors(CONFIG)
    local filters = HDG_AcqData.filters
    wipe(HDG_AcqData.filteredVendors)
    wipe(HDG_AcqData.vendorCounts)
    wipe(HDG_AcqData.vendorFilteredItems)

    for npcID, items in pairs(HDG_AcqData.vendorItems) do
        local vExp = HDG_AcqData.vendorExpansion[npcID]
        local vZone = GetVendorZone(npcID)

        -- Expansion/zone filter: skip vendors not matching
        if filters.expansion ~= "All" and vExp ~= filters.expansion then
            -- skip
        elseif filters.zone ~= "All" and vZone ~= filters.zone then
            -- skip
        else
            -- Check vendor name match upfront (cheap string op)
            local vendorNameMatch = false
            if filters.search ~= "" then
                local vName = GetVendorName(npcID)
                vendorNameMatch = vName:lower():find(filters.search, 1, true) ~= nil
            end

            -- Single pass: if vendor name matches search, filter without search and check search inline
            local total, collected = 0, 0
            local filteredList = {}

            if vendorNameMatch then
                local savedSearch = filters.search
                filters.search = ""
                local totalWithSearch, collectedWithSearch = 0, 0
                local searchList = {}
                for _, itemID in ipairs(items) do
                    if HDG_AcqData.ItemPassesFilters(itemID, CONFIG) then
                        local ic = HDG_Data.IsItemCollected(itemID)
                        total = total + 1
                        if ic then collected = collected + 1 end
                        table.insert(filteredList, itemID)
                        local nameLower = HDG_Data.GetCachedItemNameLower(itemID)
                        if nameLower:find(savedSearch, 1, true) then
                            totalWithSearch = totalWithSearch + 1
                            if ic then collectedWithSearch = collectedWithSearch + 1 end
                            table.insert(searchList, itemID)
                        end
                    end
                end
                filters.search = savedSearch
                if totalWithSearch > 0 then
                    -- Items matched search — use search-filtered counts/list
                    total, collected = totalWithSearch, collectedWithSearch
                    filteredList = searchList
                    vendorNameMatch = false
                end
            else
                for _, itemID in ipairs(items) do
                    if HDG_AcqData.ItemPassesFilters(itemID, CONFIG) then
                        total = total + 1
                        if HDG_Data.IsItemCollected(itemID) then collected = collected + 1 end
                        table.insert(filteredList, itemID)
                    end
                end
            end

            local hasMatch = total > 0
            if hasMatch then
                table.insert(HDG_AcqData.filteredVendors, npcID)
                HDG_AcqData.vendorFilteredItems[npcID] = filteredList
            end

            -- Cache counts for vendor header rows
            HDG_AcqData.vendorCounts[npcID] = { total = total, collected = collected, nameMatch = vendorNameMatch }
        end
    end

    -- Sort by vendor name
    table.sort(HDG_AcqData.filteredVendors, sortByVendorName)
end
