-- =============================================================================
-- ETB v12.61.0 — UI/MainFrame/ItemGrid.lua
-- =============================================================================
-- Fourth MainFrame submodule. Owns the item-grid data pipeline: loading
-- pages, paginating, async name resolution, rendering rows, and updating
-- collection statistics.
--
-- This is the largest functional cluster in MainFrame: 10 methods spanning
-- ~390 lines of pagination, cache management, async item-info loading, and
-- row rendering. Heavy logic, not UI construction (no CreateXxx methods
-- here — those are in the upcoming Construction cluster).
--
-- Public API (all take owner = MainFrame singleton as first arg):
--   ItemGrid:RefreshItems(owner)
--   ItemGrid:LoadPageData(owner, pageNum)
--   ItemGrid:PreloadFirstPageItemInfo(owner)
--   ItemGrid:RequestItemInfoAsync(owner)
--   ItemGrid:ClearAllItemRows(owner)
--   ItemGrid:UpdateDisplayWithRefresh(owner)
--   ItemGrid:UpdateDisplay(owner)
--   ItemGrid:UpdatePendingItemsForCurrentPage(owner)
--   ItemGrid:UpdateChangedRows(owner)
--   ItemGrid:UpdateStats(owner)
--
-- Owner contract — fields read/written:
--   owner.frame                       (with .pageText, .itemCountText, .prevBtn,
--                                      .nextBtn, .collectionStats, .statsOverall,
--                                      .statsSlot, .statsShowing)
--   owner.items                       (current page's item list)
--   owner._filteredItems              (full filtered+sorted list across all pages)
--   owner.itemRows                    (reusable row pool)
--   owner.currentPage, .itemsPerPage  (pagination state)
--   owner.totalItemCount              (length of _filteredItems)
--   owner.lastPageLoaded              (last page LoadPageData processed)
--
-- Owner methods called (delegators in main file):
--   owner:ClearAllItemRows()              (this cluster, self-call OK)
--   owner:UpdateDisplay()                 (this cluster, self-call OK)
--   owner:UpdateStats()                   (this cluster, self-call OK)
--   owner:LoadPageData(pageNum)           (this cluster, self-call OK)
--   owner:PreloadFirstPageItemInfo()      (this cluster, self-call OK)
--   owner:RequestItemInfoAsync()          (this cluster, self-call OK)
--   owner:UpdateCurrentViewingLabel(name) (LivePreview cluster)
--
-- Globals/Blizzard:
--   GetItemInfo, GetItemInfoInstant, wipe, collectgarbage
--   ETB.Filtering, ETB.BlizzardData, ETB.ArmorData, ETB.WeaponData,
--   ETB.ItemRow, ETB.ModelPreview, ETB.CATEGORY_INFO
-- =============================================================================

local ADDON_NAME, NS = ...
local ETB = NS.ETB

NS.ETB.MainFrame = NS.ETB.MainFrame or ETB.MainFrame or {}
local ItemGrid = {}
NS.ETB.MainFrame.ItemGrid = ItemGrid

-- =============================================================================
-- REFRESH — full reload after category/filter change
-- =============================================================================

function ItemGrid:RefreshItems(owner)
    if not owner.frame then return end

    -- Hide hover preview when changing categories/refreshing
    if ETB.ModelPreview then
        ETB.ModelPreview:Hide()
    end

    -- Free old data before allocating new
    if owner._filteredItems then
        wipe(owner._filteredItems)
        owner._filteredItems = nil
    end
    owner.items = {}
    owner.lastPageLoaded = 0

    -- Clear all ItemRow data
    owner:ClearAllItemRows()

    -- Clear ArmorData and WeaponData caches (needed when onlyUsable changes)
    if ETB.ArmorData and ETB.ArmorData.InvalidateCache then
        ETB.ArmorData:InvalidateCache()
    end
    if ETB.WeaponData and ETB.WeaponData.InvalidateCache then
        ETB.WeaponData:InvalidateCache()
    end

    -- AGGRESSIVE: Clear ALL category caches except current one
    -- Each cached category holds 500-800 item tables with alternatives = 2-5MB each
    if ETB.BlizzardData and ETB.BlizzardData.cache then
        local currentCat = ETB.Filtering and ETB.Filtering.filters.category
        for catID in pairs(ETB.BlizzardData.cache.categories) do
            if catID ~= currentCat then
                ETB.BlizzardData.cache.categories[catID] = nil
            end
        end
        -- Trim source cache aggressively
        local srcCount = 0
        for _ in pairs(ETB.BlizzardData.cache.sources) do srcCount = srcCount + 1 end
        if srcCount > 200 then
            wipe(ETB.BlizzardData.cache.sources)
        end
    end

    -- Load ALL items for category, filter, and sort — then paginate from result.
    owner.currentPage = 1
    owner._filteredItems = ETB.Filtering:GetItems({ limit = 100000 })
    owner.totalItemCount = #owner._filteredItems

    -- Slice first page
    local startIdx = 1
    local endIdx = owner.itemsPerPage

    owner.items = {}
    for i = startIdx, math.min(endIdx, #owner._filteredItems) do
        if owner._filteredItems[i] then
            local item = owner._filteredItems[i]
            if item.itemID and ETB.BlizzardData and ETB.BlizzardData.itemCache then
                local cached = ETB.BlizzardData.itemCache:Get(item.itemID)
                if cached then item = cached end
            end
            owner.items[#owner.items + 1] = item
        end
    end

    owner.lastPageLoaded = 1
    collectgarbage("step", 100)

    owner:UpdateDisplay()
    owner:UpdateStats()
end

-- =============================================================================
-- PAGINATION — load a specific page from cached _filteredItems
-- =============================================================================

function ItemGrid:LoadPageData(owner, pageNum)
    if not owner.frame then return end
    if owner.lastPageLoaded == pageNum and #owner.items > 0 then
        return
    end

    owner.items = {}
    owner.lastPageLoaded = pageNum

    -- Use cached filtered results if available, otherwise reload
    if not owner._filteredItems or #owner._filteredItems == 0 then
        owner._filteredItems = ETB.Filtering:GetItems({ limit = 100000 })
        owner.totalItemCount = #owner._filteredItems
    end

    local startIdx = (pageNum - 1) * owner.itemsPerPage + 1
    local endIdx = startIdx + owner.itemsPerPage - 1

    for i = startIdx, math.min(endIdx, #owner._filteredItems) do
        if owner._filteredItems[i] then
            local item = owner._filteredItems[i]
            if item.itemID and ETB.BlizzardData and ETB.BlizzardData.itemCache then
                local cached = ETB.BlizzardData.itemCache:Get(item.itemID)
                if cached then
                    item = cached
                end
            end
            owner.items[#owner.items + 1] = item
        end
    end

    owner:UpdateDisplay()

    if pageNum == 1 then
        owner:PreloadFirstPageItemInfo()
    else
        owner:RequestItemInfoAsync()
    end
end

-- =============================================================================
-- ASYNC NAME RESOLUTION
-- =============================================================================

function ItemGrid:PreloadFirstPageItemInfo(owner)
    -- Pre-load item info for first page - try both GetItemInfoInstant and GetItemInfo
    if not ETB.BlizzardData then return end

    local updated = false
    local processed = 0
    local maxPerLoad = 20  -- Process all items on first page

    for i, item in ipairs(owner.items) do
        if item and item.itemID and processed < maxPerLoad then
            -- Get the cached version to ensure we're updating the right object
            local cached = ETB.BlizzardData.itemCache and ETB.BlizzardData.itemCache:Get(item.itemID)
            if cached then
                item = cached  -- Use cached version
                owner.items[i] = cached  -- Update reference in items array
            end

            -- Check if we need to load the name
            if not item.name or item.name == "Loading..." or item.name == "Unknown" or type(item.name) == "number" then
                local name, link, quality, itemLevel, minLevel, itemType, itemSubType, stackCount, equipLoc, texture

                -- Try GetItemInfo first (works if item is already cached by WoW)
                name, link, quality, itemLevel, minLevel, itemType, itemSubType, stackCount, equipLoc, texture = GetItemInfo(item.itemID)

                -- If GetItemInfo didn't work, try GetItemInfoInstant for icon only
                -- NOTE: GetItemInfoInstant returns (itemID, itemType, itemSubType, equipLoc, icon, classID, subclassID)
                -- It does NOT return name/link/quality — different signature from GetItemInfo!
                if (not name or name == "") and GetItemInfoInstant then
                    local _, _, _, instEquipLoc, instTexture = GetItemInfoInstant(item.itemID)
                    -- Only use for icon and equipLoc, NOT for name/quality
                    texture = instTexture
                    equipLoc = instEquipLoc
                end

                -- If we got a name, update immediately
                if name and name ~= "" then
                    item.name = name
                    if type(quality) == "number" then item.quality = quality end
                    item.icon = texture or item.icon
                    item.invType = equipLoc or item.invType
                    updated = true
                else
                    -- Request async loading
                    ETB.BlizzardData.pendingItemInfo[item.itemID] = true
                    GetItemInfo(item.itemID)  -- Request async
                end
                processed = processed + 1
            end
        end
    end

    -- If we got instant results, update display immediately
    if updated then
        owner:UpdateDisplay()
    end

    -- Also try to update any async items that might have loaded
    local asyncUpdated = false
    for _ = 1, 5 do  -- More passes to catch items
        if ETB.BlizzardData:UpdatePendingItems() then
            asyncUpdated = true
        end
    end
    if asyncUpdated then
        -- Refresh item references from cache before updating display
        for i, item in ipairs(owner.items) do
            if item and item.itemID and ETB.BlizzardData.itemCache then
                local cached = ETB.BlizzardData.itemCache:Get(item.itemID)
                if cached then
                    owner.items[i] = cached
                end
            end
        end
        owner:UpdateDisplay()
    end
end

function ItemGrid:RequestItemInfoAsync(owner)
    -- Request item info async for all items on the current page
    if not ETB.BlizzardData then return end

    for _, item in ipairs(owner.items) do
        if item and item.itemID and
           (not item.name or item.name == "Loading..." or item.name == "Unknown" or type(item.name) == "number") then
            ETB.BlizzardData.pendingItemInfo[item.itemID] = true
            GetItemInfo(item.itemID)  -- Request async
        end
    end

    -- Try to update items immediately if available
    local updated = false

    for _, item in ipairs(owner.items) do
        if item and item.itemID and ETB.BlizzardData.pendingItemInfo[item.itemID] then
            if ETB.BlizzardData:UpdateItemInfo(item.itemID) then
                ETB.BlizzardData.pendingItemInfo[item.itemID] = nil
                updated = true
            end
        end
    end

    if updated then
        owner:UpdateDisplay()
    end
end

-- =============================================================================
-- ROW MANAGEMENT
-- =============================================================================

function ItemGrid:ClearAllItemRows(owner)
    -- Clear itemData from all rows to free memory
    for _, row in ipairs(owner.itemRows) do
        if row then
            row.itemData = nil
            row.index = nil
            row:Hide()
        end
    end
end

function ItemGrid:UpdateDisplayWithRefresh(owner)
    -- Force clear old data first
    owner:ClearAllItemRows()

    -- Load new page data
    owner:LoadPageData(owner.currentPage)

    -- Display is already updated in LoadPageData if items were updated
    -- Just ensure display is current
    owner:UpdateDisplay()
end

function ItemGrid:UpdateDisplay(owner)
    if not owner.frame then return end

    local startIdx = (owner.currentPage - 1) * owner.itemsPerPage + 1

    -- Refresh item references from cache before displaying to ensure we have latest data
    if ETB.BlizzardData and ETB.BlizzardData.itemCache then
        for i, item in ipairs(owner.items) do
            if item and item.itemID then
                local cached = ETB.BlizzardData.itemCache:Get(item.itemID)
                if cached then
                    owner.items[i] = cached  -- Use cached version with latest data
                end
            end
        end
    end

    -- Update visible rows and clear unused ones
    for i, row in ipairs(owner.itemRows) do
        local item = owner.items[i]  -- Items array now only contains current page
        if item then
            ETB.ItemRow:Update(row, item, startIdx + i - 1)
            row:Show()
        else
            -- Clear data from unused rows
            row.itemData = nil
            row.index = nil
            row:Hide()
        end
    end

    local totalPages = math.max(1, math.ceil(owner.totalItemCount / owner.itemsPerPage))
    owner.frame.pageText:SetText(string.format("Page %d / %d", owner.currentPage, totalPages))
    owner.frame.itemCountText:SetText(string.format("%d Appearances", owner.totalItemCount))
    owner.frame.prevBtn:SetEnabled(owner.currentPage > 1)
    owner.frame.nextBtn:SetEnabled(owner.currentPage < totalPages)

    -- Collection stats
    if owner.frame.collectionStats then
        local collected = 0
        for _, item in ipairs(owner.items) do
            if item and item.isCollected then collected = collected + 1 end
        end
        local shown = #owner.items
        if shown > 0 then
            local pct = math.floor(100 * collected / owner.totalItemCount + 0.5)
            owner.frame.collectionStats:SetText(string.format(
                "|cff00ff00%d|r|cff888888 / %d collected (%d%%)|r", collected, owner.totalItemCount, pct))
        else
            owner.frame.collectionStats:SetText("")
        end
    end
end

function ItemGrid:UpdatePendingItemsForCurrentPage(owner)
    -- Only update items that are on the current page to reduce work
    if not ETB.BlizzardData or not ETB.BlizzardData.pendingItemInfo then
        return false
    end

    local updated = false
    local processed = 0
    local maxPerTick = 5  -- Limit to 5 items per tick to avoid lag

    -- Check only items on current page
    for _, item in ipairs(owner.items) do
        if item and item.itemID and ETB.BlizzardData.pendingItemInfo[item.itemID] then
            if ETB.BlizzardData:UpdateItemInfo(item.itemID) then
                ETB.BlizzardData.pendingItemInfo[item.itemID] = nil
                updated = true
                processed = processed + 1
                if processed >= maxPerTick then
                    break  -- Don't process too many at once
                end
            end
        end
    end

    return updated
end

function ItemGrid:UpdateChangedRows(owner)
    -- Only update rows whose items have changed, not all rows
    if not owner.frame then return end

    local startIdx = (owner.currentPage - 1) * owner.itemsPerPage + 1

    for i, row in ipairs(owner.itemRows) do
        local item = owner.items[i]
        if item then
            -- Check if this row needs updating
            local needsUpdate = false
            if not row.itemData or row.itemData.itemID ~= item.itemID then
                -- Different item or no item data
                needsUpdate = true
            elseif row.itemData.name ~= item.name then
                -- Name changed
                needsUpdate = true
            end

            if needsUpdate then
                ETB.ItemRow:Update(row, item, startIdx + i - 1)
                row:Show()
            end
        elseif row:IsShown() then
            -- Item removed, hide row
            row.itemData = nil
            row.index = nil
            row:Hide()
        end
    end
end

-- =============================================================================
-- STATS — collection counts in bottom bar + sidebar
-- =============================================================================

function ItemGrid:UpdateStats(owner)
    if not owner.frame then return end

    -- Overall stats
    local allStats = ETB.BlizzardData:GetAllStats()
    local overallElement = owner.frame.statsOverall
    if overallElement then
        overallElement:SetText(string.format("|cffffd100Overall:|r %d/%d (%.1f%%)",
            allStats.collected, allStats.total, allStats.percent))
    end

    -- Current slot stats
    local categoryID = ETB.Filtering.filters.category
    local slotStats = ETB.BlizzardData:GetCollectionStats(categoryID)
    local catInfo = ETB.CATEGORY_INFO[categoryID]
    local slotElement = owner.frame.statsSlot
    if slotElement then
        slotElement:SetText(string.format("|cffffd100%s:|r %d/%d (%.1f%%)",
            catInfo and catInfo.name or "Slot",
            slotStats.collected, slotStats.total, slotStats.percent))
    end

    -- Current view count: collected-of-filtered ratio (Feature 1.5)
    local showingElement = owner.frame.statsShowing
    if showingElement then
        local total = owner.totalItemCount or 0
        if total == 0 then
            showingElement:SetText("Showing: 0 items")
        else
            local collectedCount = 0
            if owner._filteredItems then
                for i = 1, #owner._filteredItems do
                    if owner._filteredItems[i].isCollected then
                        collectedCount = collectedCount + 1
                    end
                end
            end
            local pct = total > 0 and (collectedCount / total * 100) or 0
            showingElement:SetText(string.format(
                "Showing: |cff00ff00%d|r/|cffffd100%d|r collected (%.1f%%)",
                collectedCount, total, pct))
        end
    end

    -- Update the "Currently Viewing" label too
    owner:UpdateCurrentViewingLabel(catInfo and catInfo.name or "Unknown")
end
