--[[
============================================================================
ETB v11.0 - MainFrame.lua (V8 INTEGRATED - COMPREHENSIVE FIX)
============================================================================
Fixes:
1. Pagination triggers item name refresh
2. Hover preview (ModelPreview popup) properly shows on hover
3. Live Preview with slot buttons + remove/hide icons
4. Accurate per-slot collection stats
5. Clear indication of current slot/weapon type being viewed

Preview Separation:
- HOVER PREVIEW: ETB.ModelPreview floating popup (single item, resets)
- LIVE PREVIEW: Right panel for building outfits (stacks items)
============================================================================
]]--

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

ETB.MainFrame = {
    frame = nil,
    items = {},
    totalItemCount = 0,  -- Total count without loading all items
    itemRows = {},
    currentPage = 1,
    itemsPerPage = 19,  -- 19 rows x 1 column
    categoryMode = "armor",
    previewRotation = 0,
    livePreviewSlots = {},  -- Track what's in each slot
    lastPageLoaded = 0,  -- Track which page we last loaded
}

local MF = ETB.MainFrame

-- =============================================================================
-- CONSTANTS - Carefully measured for proper layout
-- =============================================================================

local FRAME_WIDTH = 1350
local FRAME_HEIGHT = 850
local SIDEBAR_WIDTH = 210
local PREVIEW_WIDTH = 340
local ITEM_ROW_WIDTH = 750
local ITEM_ROW_HEIGHT = 32
local ROWS_PER_COLUMN = 19
local COLUMNS = 1

-- Layout measurements
local FRAME_MARGIN = 15
local TITLE_AREA_HEIGHT = 45
local TOPBAR_HEIGHT = 95
local BOTTOMBAR_HEIGHT = 50

-- Calculated positions
local TOPBAR_Y = -(TITLE_AREA_HEIGHT)
local CONTENT_Y = -(TITLE_AREA_HEIGHT + TOPBAR_HEIGHT + 8)
local BOTTOM_MARGIN = FRAME_MARGIN + BOTTOMBAR_HEIGHT + 8

-- NOTE: Armor slots and weapon categories are now selected via dropdowns in the sidebar
-- The actual category definitions are in ETB.CATEGORY_INFO (Core/Init.lua)

-- Live Preview slot layout (left and right of model)
local PREVIEW_SLOTS_LEFT = { "Head", "Shoulder", "Back", "Chest", "Shirt", "Tabard", "Wrist" }
local PREVIEW_SLOTS_RIGHT = { "Hands", "Waist", "Legs", "Feet", "MainHand", "OffHand" }

-- =============================================================================
-- CREATE MAIN FRAME
-- =============================================================================

function MF:Create()
    if self.frame then return self.frame end
    
    local f = CreateFrame("Frame", "ETBMainFrame", UIParent, "BackdropTemplate")
    f:SetSize(FRAME_WIDTH, FRAME_HEIGHT)
    f:SetPoint("CENTER")
    f:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        tile = true, tileSize = 32, edgeSize = 32,
        insets = { left = 8, right = 8, top = 8, bottom = 8 }
    })
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetFrameStrata("HIGH")
    f:Hide()
    
    -- Hide ModelPreview when main frame is hidden (ESC, close button, etc.)
    f:SetScript("OnHide", function()
        if ETB.ModelPreview then
            ETB.ModelPreview:Hide()
        end
        if ETB.AppearanceDetail then
            ETB.AppearanceDetail:Hide()
        end
        -- Free the big filtered items cache
        if MF._filteredItems then
            wipe(MF._filteredItems)
            MF._filteredItems = nil
        end
        -- Clear current page items
        if MF.items then
            wipe(MF.items)
        end
        -- Wipe ALL BlizzardData caches
        if ETB.BlizzardData then
            if ETB.BlizzardData.cache then
                wipe(ETB.BlizzardData.cache.categories)
                wipe(ETB.BlizzardData.cache.sources)
            end
            -- Clear item cache (500 items × 1.2KB = 600KB)
            if ETB.BlizzardData.itemCache and ETB.BlizzardData.itemCache.Clear then
                ETB.BlizzardData.itemCache:Clear()
            end
        end
        -- Force TWO GC cycles — first marks, second frees
        collectgarbage("collect")
        collectgarbage("collect")
    end)
    
    -- ESC closes frame (taint-safe: avoids modifying UISpecialFrames)
    f:SetScript("OnKeyDown", function(self, key)
        if key == "ESCAPE" then
            pcall(self.SetPropagateKeyboardInput, self, false)
            self:Hide()
        else
            pcall(self.SetPropagateKeyboardInput, self, true)
        end
    end)
    
    f.closeBtn = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    f.closeBtn:SetPoint("TOPRIGHT", -8, -8)
    
    -- Toggle button: switch to ERB Raid Browser
    f.raidToggle = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    f.raidToggle:SetSize(80, 22)
    f.raidToggle:SetPoint("TOPRIGHT", -38, -14)
    f.raidToggle:SetText("Raid BiS")
    f.raidToggle:SetScript("OnClick", function()
        if ETB.ToggleUIMode then ETB:ToggleUIMode() end
    end)
    f.raidToggle:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
        GameTooltip:AddLine("Switch to Raid BiS Browser")
        GameTooltip:AddLine("View BiS gear rankings per slot", 1, 1, 1)
        GameTooltip:Show()
    end)
    f.raidToggle:SetScript("OnLeave", function() GameTooltip:Hide() end)
    
    -- Wishlist button
    f.wishlistToggle = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    f.wishlistToggle:SetSize(80, 22)
    f.wishlistToggle:SetPoint("RIGHT", f.raidToggle, "LEFT", -3, 0)
    f.wishlistToggle:SetText("Wishlist")
    f.wishlistToggle:SetScript("OnClick", function()
        if ETB.WishlistPanel then ETB.WishlistPanel:Toggle() end
    end)
    f.wishlistToggle:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
        GameTooltip:AddLine("Open Wishlist")
        local count = ETB.Wishlist and ETB.Wishlist:GetItemCount() or 0
        GameTooltip:AddLine(count .. " items on your wishlist", 1, 1, 1)
        GameTooltip:Show()
    end)
    f.wishlistToggle:SetScript("OnLeave", function() GameTooltip:Hide() end)
    
    f.title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    f.title:SetPoint("TOPLEFT", 14, -16)
    f.title:SetText("|cffffd100Enhanced Transmog Browser|r")
    
    f.version = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.version:SetPoint("LEFT", f.title, "RIGHT", 6, 0)
    f.version:SetText("|cff555555v" .. (ETB.version or "12.0") .. "|r")
    
    self:CreateTopBar(f)
    self:CreateLeftSidebar(f)
    self:CreateItemGrid(f)
    self:CreateLivePreview(f)
    self:CreateBottomBar(f)
    
    self.frame = f
    return f
end

-- =============================================================================
-- TOP BAR - With clear current category display
-- =============================================================================

function MF:CreateTopBar(f)
    local topBar = CreateFrame("Frame", nil, f, "BackdropTemplate")
    topBar:SetPoint("TOPLEFT", FRAME_MARGIN, TOPBAR_Y)
    topBar:SetPoint("TOPRIGHT", -FRAME_MARGIN, TOPBAR_Y)
    topBar:SetHeight(TOPBAR_HEIGHT)
    topBar:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    f.topBar = topBar
    
    -- Row 1: Current slot + Sort
    f.currentViewingLabel = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    f.currentViewingLabel:SetPoint("TOPLEFT", 10, -6)
    f.currentViewingLabel:SetText("|cff00ff00Head|r")
    
    -- Sort dropdown (right side)
    local sortLabel = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    sortLabel:SetPoint("TOPRIGHT", -120, -8)
    sortLabel:SetText("|cffffffffSort:|r")
    
    f.sortDropdown = ETBDropdown:Create("ETBSortDropdown", topBar)
    f.sortDropdown:SetPoint("TOPRIGHT", -8, -4)
    ETBDropdown:SetWidth(f.sortDropdown, 110)
    ETBDropdown:SetText(f.sortDropdown, "Name (A-Z)")
    ETBDropdown:Initialize(f.sortDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        for _, s in ipairs({
            { text = "Name (A-Z)", sort = "name", asc = true },
            { text = "Name (Z-A)", sort = "name", asc = false },
            { text = "Quality " .. string.char(226,134,145), sort = "quality", asc = true },
            { text = "Quality " .. string.char(226,134,147), sort = "quality", asc = false },
        }) do
            info.text = s.text
            info.func = function()
                ETBDropdown:SetText(f.sortDropdown, s.text)
                ETB.Filtering:SetSort(s.sort, s.asc)
                MF:RefreshItems()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    
    -- Armor slot icons (Row 1, after label)
    f.slotIcons = {}
    f.weaponTypeIcons = {}
    
    local armorLabel = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    armorLabel:SetPoint("TOPLEFT", 10, -30)
    armorLabel:SetText("|cffffd100Armor:|r")
    
    local armorSlots = {
        { id = 1,  name = "Head" },
        { id = 2,  name = "Shoulder" },
        { id = 3,  name = "Back" },
        { id = 4,  name = "Chest" },
        { id = 5,  name = "Shirt" },
        { id = 6,  name = "Tabard" },
        { id = 7,  name = "Wrist" },
        { id = 8,  name = "Hands" },
        { id = 9,  name = "Waist" },
        { id = 10, name = "Legs" },
        { id = 11, name = "Feet" },
    }
    
    local BUTTON_SIZE = 28
    local BUTTON_SPACING = 30
    local ARMOR_ROW_START_X = 55
    local ARMOR_ROW_Y = -26
    
    for i, slot in ipairs(armorSlots) do
        local btn = CreateFrame("Button", nil, topBar)
        btn:SetSize(BUTTON_SIZE, BUTTON_SIZE)
        btn:SetPoint("TOPLEFT", ARMOR_ROW_START_X + (i-1) * BUTTON_SPACING, ARMOR_ROW_Y)
        
        btn.icon = btn:CreateTexture(nil, "ARTWORK")
        btn.icon:SetAllPoints()
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[slot.id]
        btn.icon:SetTexture(catInfo and catInfo.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
        btn.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        
        btn.selected = btn:CreateTexture(nil, "OVERLAY")
        btn.selected:SetAllPoints()
        btn.selected:SetColorTexture(0, 1, 0, 0.4)
        btn.selected:Hide()
        
        btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
        
        btn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
            GameTooltip:SetText(slot.name)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        btn:SetScript("OnClick", function()
            ETB.Filtering:SetCategory(slot.id)
            MF.categoryMode = "armor"
            MF:UpdateSlotHighlight(slot.id)
            MF:UpdateCategoryTabs()
            MF:UpdateCurrentViewingLabel(slot.name)
            MF:UpdateWeaponCategoryUI()
            MF:RefreshItems()
        end)
        
        btn.slotID = slot.id
        btn.slotName = slot.name
        f.slotIcons[slot.id] = btn
    end
    
    -- ==========================================================================
    -- ROW 3: Weapon buttons (16 buttons organized in groups)
    -- ==========================================================================
    
    local WEAPON_ROW_Y = -60
    
    -- 1H Weapons (6 buttons)
    local label1H = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    label1H:SetPoint("TOPLEFT", 10, WEAPON_ROW_Y - 3)
    label1H:SetText("|cffffd1001H:|r")
    
    local weapons1H = {
        { id = 14, name = "1H Swords" },
        { id = 13, name = "1H Axes" },
        { id = 15, name = "1H Maces" },
        { id = 16, name = "Daggers" },
        { id = 17, name = "Fist Weapons" },
        { id = 12, name = "Wands" },
    }
    
    local START_1H = 35
    for i, slot in ipairs(weapons1H) do
        local btn = CreateFrame("Button", nil, topBar)
        btn:SetSize(BUTTON_SIZE, BUTTON_SIZE)
        btn:SetPoint("TOPLEFT", START_1H + (i-1) * BUTTON_SPACING, WEAPON_ROW_Y)
        
        btn.icon = btn:CreateTexture(nil, "ARTWORK")
        btn.icon:SetAllPoints()
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[slot.id]
        btn.icon:SetTexture(catInfo and catInfo.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
        btn.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        
        btn.selected = btn:CreateTexture(nil, "OVERLAY")
        btn.selected:SetAllPoints()
        btn.selected:SetColorTexture(0, 1, 0, 0.4)
        btn.selected:Hide()
        
        btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
        
        btn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
            GameTooltip:SetText(slot.name)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        btn:SetScript("OnClick", function()
            ETB.Filtering:SetCategory(slot.id)
            MF.categoryMode = "weapons"
            MF:UpdateSlotHighlight(slot.id)
            MF:UpdateCategoryTabs()
            local catInfo2 = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[slot.id]
            MF:UpdateCurrentViewingLabel(catInfo2 and catInfo2.name or slot.name)
            MF:UpdateWeaponCategoryUI()
            MF:RefreshItems()
        end)
        
        btn.slotID = slot.id
        btn.slotName = slot.name
        f.slotIcons[slot.id] = btn
    end
    
    -- 2H Weapons (5 buttons)
    local label2H = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    label2H:SetPoint("TOPLEFT", 225, WEAPON_ROW_Y - 3)
    label2H:SetText("|cffffd1002H:|r")
    
    local weapons2H = {
        { id = 21, name = "2H Swords" },
        { id = 20, name = "2H Axes" },
        { id = 22, name = "2H Maces" },
        { id = 23, name = "Staves" },
        { id = 24, name = "Polearms" },
    }
    
    local START_2H = 250
    for i, slot in ipairs(weapons2H) do
        local btn = CreateFrame("Button", nil, topBar)
        btn:SetSize(BUTTON_SIZE, BUTTON_SIZE)
        btn:SetPoint("TOPLEFT", START_2H + (i-1) * BUTTON_SPACING, WEAPON_ROW_Y)
        
        btn.icon = btn:CreateTexture(nil, "ARTWORK")
        btn.icon:SetAllPoints()
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[slot.id]
        btn.icon:SetTexture(catInfo and catInfo.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
        btn.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        
        btn.selected = btn:CreateTexture(nil, "OVERLAY")
        btn.selected:SetAllPoints()
        btn.selected:SetColorTexture(0, 1, 0, 0.4)
        btn.selected:Hide()
        
        btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
        
        btn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
            GameTooltip:SetText(slot.name)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        btn:SetScript("OnClick", function()
            ETB.Filtering:SetCategory(slot.id)
            MF.categoryMode = "weapons"
            MF:UpdateSlotHighlight(slot.id)
            MF:UpdateCategoryTabs()
            local catInfo2 = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[slot.id]
            MF:UpdateCurrentViewingLabel(catInfo2 and catInfo2.name or slot.name)
            MF:UpdateWeaponCategoryUI()
            MF:RefreshItems()
        end)
        
        btn.slotID = slot.id
        btn.slotName = slot.name
        f.slotIcons[slot.id] = btn
    end
    
    -- Ranged + Off-Hand (5 buttons)
    local labelRanged = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    labelRanged:SetPoint("TOPLEFT", 410, WEAPON_ROW_Y - 3)
    labelRanged:SetText("|cffffd100Rng/OH:|r")
    
    local weaponsRanged = {
        { id = 25, name = "Bows" },
        { id = 26, name = "Guns" },
        { id = 27, name = "Crossbows" },
        { id = 18, name = "Shields" },
        { id = 19, name = "Off-Hands" },
    }
    
    local START_RANGED = 480
    for i, slot in ipairs(weaponsRanged) do
        local btn = CreateFrame("Button", nil, topBar)
        btn:SetSize(BUTTON_SIZE, BUTTON_SIZE)
        btn:SetPoint("TOPLEFT", START_RANGED + (i-1) * BUTTON_SPACING, WEAPON_ROW_Y)
        
        btn.icon = btn:CreateTexture(nil, "ARTWORK")
        btn.icon:SetAllPoints()
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[slot.id]
        btn.icon:SetTexture(catInfo and catInfo.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
        btn.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        
        btn.selected = btn:CreateTexture(nil, "OVERLAY")
        btn.selected:SetAllPoints()
        btn.selected:SetColorTexture(0, 1, 0, 0.4)
        btn.selected:Hide()
        
        btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
        
        btn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
            GameTooltip:SetText(slot.name)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        btn:SetScript("OnClick", function()
            ETB.Filtering:SetCategory(slot.id)
            MF.categoryMode = "weapons"
            MF:UpdateSlotHighlight(slot.id)
            MF:UpdateCategoryTabs()
            local catInfo2 = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[slot.id]
            MF:UpdateCurrentViewingLabel(catInfo2 and catInfo2.name or slot.name)
            MF:UpdateWeaponCategoryUI()
            MF:RefreshItems()
        end)
        
        btn.slotID = slot.id
        btn.slotName = slot.name
        f.slotIcons[slot.id] = btn
    end
    
    -- Hidden placeholder frames for compatibility
    f.weaponTypeLabel = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.weaponTypeLabel:Hide()
    f.weaponTypeDropdown = ETBDropdown:Create("ETBWeaponTypeDropdown", topBar)
    f.weaponTypeDropdown:Hide()
    f.weaponTypeIconsFrame = CreateFrame("Frame", nil, topBar)
    f.weaponTypeIconsFrame:Hide()
    f.weaponTypeSectionLabel = topBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.weaponTypeSectionLabel:Hide()
end

function MF:UpdateSlotHighlight(selectedID)
    if not self.frame then return end
    -- Update all slot buttons (indexed by slot ID)
    for slotID, btn in pairs(self.frame.slotIcons) do
        if btn and btn.selected then
            btn.selected:SetShown(slotID == selectedID)
        end
    end
end

function MF:UpdateWeaponTypeHighlight(selectedType)
    if not self.frame then return end
    -- Update all weapon type buttons
    for weaponType, btn in pairs(self.frame.weaponTypeIcons or {}) do
        if btn and btn.selected then
            -- Highlight if this is the selected type, or if "all" is selected and this is "all"
            local shouldHighlight = (weaponType == selectedType) or (selectedType == nil and weaponType == "all")
            btn.selected:SetShown(shouldHighlight)
        end
    end
end

function MF:UpdateCurrentViewingLabel(slotName)
    if not self.frame then return end
    local categoryID = ETB.Filtering.filters.category
    local stats = ETB.BlizzardData:GetCollectionStats(categoryID)
    
    -- Check if viewing as different character
    local profileInfo = ""
    if ETB.ProfileManager then
        local PM = ETB.ProfileManager
        if PM.activeProfileKey and PM.currentCharacterKey and PM.activeProfileKey ~= PM.currentCharacterKey then
            local profile = PM:GetActiveProfile()
            if profile then
                profileInfo = string.format(" |cffff8800[As %s - %s]|r", profile.name, profile.classID)
            end
        end
    end
    
    self.frame.currentViewingLabel:SetText(string.format(
        "|cff00ff00%s|r |cff888888%d/%d (%.0f%%)|r%s",
        slotName or "Unknown",
        stats.collected, stats.total, stats.percent,
        profileInfo
    ))
end

-- =============================================================================
-- LEFT SIDEBAR
-- =============================================================================

function MF:CreateLeftSidebar(f)
    local sidebar = CreateFrame("Frame", nil, f, "BackdropTemplate")
    sidebar:SetWidth(SIDEBAR_WIDTH)
    sidebar:SetPoint("TOPLEFT", FRAME_MARGIN, CONTENT_Y)
    sidebar:SetPoint("BOTTOMLEFT", FRAME_MARGIN, BOTTOM_MARGIN)
    sidebar:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    sidebar:SetClipsChildren(true)
    f.sidebar = sidebar
    
    -- ==========================================================================
    -- SIDEBAR LAYOUT (all Y positions relative to sidebar top)
    -- Total height available: ~625px
    -- ==========================================================================
    
    local title = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    title:SetPoint("TOP", 0, -8)
    title:SetText("|cff00ffffFilters|r")
    
    -- Category tabs - inline row
    f.armorTab = self:CreateTab(sidebar, "Armor", 6, -26)
    f.armorTab:SetScript("OnClick", function()
        MF:HideSetsView()
        MF.categoryMode = "armor"
        MF:UpdateCategoryTabs()
        ETB.Filtering:SetCategory(1)
        MF:UpdateSlotHighlight(1)
        MF:UpdateCurrentViewingLabel("Head")
        MF:UpdateWeaponCategoryUI()
        MF:RefreshItems()
    end)
    
    f.weaponsTab = self:CreateTab(sidebar, "Weapons", 54, -26)
    f.weaponsTab:SetScript("OnClick", function()
        MF:HideSetsView()
        MF.categoryMode = "weapons"
        MF:UpdateCategoryTabs()
        ETB.Filtering:SetCategory(14)
        MF:UpdateSlotHighlight(14)
        MF:UpdateCurrentViewingLabel("1H Swords")
        MF:UpdateWeaponCategoryUI()
        MF:RefreshItems()
    end)
    
    -- Row 2: Sets (left), Builder (right)
    f.setsTab = self:CreateTab(sidebar, "Sets", 102, -26)
    f.setsTab:SetScript("OnClick", function()
        MF:HideSetBuilderView()
        MF.categoryMode = "sets"
        MF:UpdateCategoryTabs()
        MF:ShowSetsView()
    end)
    
    f.setBuilderTab = self:CreateTab(sidebar, "Builder", 150, -26)
    f.setBuilderTab:SetScript("OnClick", function()
        -- Open Set Builder as a separate standalone window
        if ETB.SetBuilder then
            ETB.SetBuilder:Show()
        end
    end)
    
    -- Section divider 2 (below 2x2 tabs)
    local divider2 = sidebar:CreateTexture(nil, "ARTWORK")
    divider2:SetSize(SIDEBAR_WIDTH - 16, 2)
    divider2:SetPoint("TOP", 0, -54)
    divider2:SetColorTexture(0.5, 0.5, 0.5, 0.5)
    
    -- CLASS FILTER SECTION
    local classLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    classLabel:SetPoint("TOPLEFT", 6, -64)
    classLabel:SetText("")
    
    -- My Class Only checkbox
    f.myClassCheck = CreateFrame("CheckButton", nil, sidebar, "UICheckButtonTemplate")
    f.myClassCheck:SetPoint("TOPLEFT", 4, -60)
    f.myClassCheck:SetChecked(false)
    ETB.Filtering:SetMyClassOnly(false)
    f.myClassCheck:SetScript("OnClick", function(self)
        ETB.Filtering:SetMyClassOnly(self:GetChecked())
        MF:RefreshItems()
    end)
    
    local checkText = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    checkText:SetPoint("LEFT", f.myClassCheck, "RIGHT", 0, 0)
    checkText:SetText("|cff00ff00My Class Only|r")
    
    -- Group by Appearance: always on (one item per unique visual appearance)
    ETB.Filtering:SetGroupByAppearance(true)
    
    -- Armor Type Filter
    local armorTypeLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    armorTypeLabel:SetPoint("TOPLEFT", 8, -84)
    armorTypeLabel:SetText("|cffffd100Armor|r")
    
    f.armorTypeDropdown = ETBDropdown:Create("ETBArmorTypeDropdown", sidebar)
    f.armorTypeDropdown:SetPoint("TOPLEFT", 0, -98)
    ETBDropdown:SetWidth(f.armorTypeDropdown, SIDEBAR_WIDTH - 30)
    ETBDropdown:SetText(f.armorTypeDropdown, "All Types")
    ETBDropdown:Initialize(f.armorTypeDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        local armorTypes = {
            { value = "all", text = "All Types" },
            { value = "PLATE", text = "Plate" },
            { value = "MAIL", text = "Mail" },
            { value = "LEATHER", text = "Leather" },
            { value = "CLOTH", text = "Cloth" },
        }
        for _, at in ipairs(armorTypes) do
            info.text = at.text
            info.value = at.value
            info.func = function()
                ETBDropdown:SetText(f.armorTypeDropdown, at.text)
                ETB.Filtering:SetArmorType(at.value)
                MF:RefreshItems()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    
    -- Note: Profile system is still loaded for tracking but UI is removed
    -- Initialize profile system silently if available
    if ETB.ProfileSelector and ETB.ProfileSelector.Initialize then
        ETB.ProfileSelector:Initialize()
    end
    
    -- Section divider 3
    local divider3 = sidebar:CreateTexture(nil, "ARTWORK")
    divider3:SetSize(SIDEBAR_WIDTH - 16, 2)
    divider3:SetPoint("TOP", 0, -124)
    divider3:SetColorTexture(0.5, 0.5, 0.5, 0.5)
    
    -- Source Type
    local sourceLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    sourceLabel:SetPoint("TOPLEFT", 8, -130)
    sourceLabel:SetText("|cffffd100Source|r")
    
    f.sourceDropdown = ETBDropdown:Create("ETBSourceDropdown", sidebar)
    f.sourceDropdown:SetPoint("TOPLEFT", 0, -144)
    ETBDropdown:SetWidth(f.sourceDropdown, SIDEBAR_WIDTH - 30)
    ETBDropdown:SetText(f.sourceDropdown, "All Sources")
    ETBDropdown:Initialize(f.sourceDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        local sourceTypes = {
            { value = "all",        text = "All Sources" },
            { value = "raid",       text = "Raid" },
            { value = "dungeon",    text = "Dungeon" },
            { value = "quest",      text = "Quest" },
            { value = "vendor",     text = "Vendor" },
            { value = "crafted",    text = "Crafted" },
            { value = "pvp",        text = "PvP" },
            { value = "world_drop", text = "World Drop" },
        }
        for _, st in ipairs(sourceTypes) do
            info.text = st.text
            info.value = st.value
            info.func = function()
                ETBDropdown:SetText(f.sourceDropdown, st.text)
                ETB.Filtering:SetSourceType(st.value)
                MF:RefreshItems()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    
    -- Expansion Filter
    local expansionLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    expansionLabel:SetPoint("TOPLEFT", 8, -170)
    expansionLabel:SetText("|cffffd100Expansion|r")
    
    f.expansionDropdown = ETBDropdown:Create("ETBExpansionDropdown", sidebar)
    f.expansionDropdown:SetPoint("TOPLEFT", 0, -184)
    ETBDropdown:SetWidth(f.expansionDropdown, SIDEBAR_WIDTH - 30)
    ETBDropdown:SetText(f.expansionDropdown, "All Expansions")
    ETBDropdown:Initialize(f.expansionDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        for _, exp in ipairs(ETB.EXPANSIONS) do
            info.text = exp.name
            info.value = exp.id
            info.func = function()
                ETBDropdown:SetText(f.expansionDropdown, exp.name)
                ETB.Filtering:SetExpansion(exp.id)
                MF:RefreshItems()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    
    -- Color Filter
    local colorLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    colorLabel:SetPoint("TOPLEFT", 8, -210)
    colorLabel:SetText("|cffffd100Color|r")
    
    f.colorDropdown = ETBDropdown:Create("ETBColorDropdown", sidebar)
    f.colorDropdown:SetPoint("TOPLEFT", 0, -224)
    ETBDropdown:SetWidth(f.colorDropdown, SIDEBAR_WIDTH - 30)
    ETBDropdown:SetText(f.colorDropdown, "All Colors")
    ETBDropdown:Initialize(f.colorDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        info.text = "All Colors"
        info.value = nil
        info.func = function()
            ETBDropdown:SetText(f.colorDropdown, "All Colors")
            ETB.Filtering:SetColorFilter(nil)
            MF:RefreshItems()
        end
        ETBDropdown:AddButton(_items_, info)
        
        if ETB.ColorSearch then
            for _, cat in ipairs(ETB.ColorSearch:GetCategories()) do
                info.text = cat.name
                info.value = cat.id
                info.func = function()
                    ETBDropdown:SetText(f.colorDropdown, cat.name)
                    ETB.Filtering:SetColorFilter(cat.id)
                    MF:RefreshItems()
                end
                ETBDropdown:AddButton(_items_, info)
            end
        end
    end)
    
    -- Section divider 4 - Collection filter section
    local divider4 = sidebar:CreateTexture(nil, "ARTWORK")
    divider4:SetSize(SIDEBAR_WIDTH - 16, 2)
    divider4:SetPoint("TOP", 0, -250)
    divider4:SetColorTexture(0.5, 0.5, 0.5, 0.5)
    
    -- Collection Filter Section
    local collectionLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    collectionLabel:SetPoint("TOPLEFT", 8, -256)
    collectionLabel:SetText("|cffffd100Collection|r")
    
    -- Collection Filter Buttons
    f.collAllBtn = CreateFrame("Button", nil, sidebar, "UIPanelButtonTemplate")
    f.collAllBtn:SetSize(SIDEBAR_WIDTH - 20, 24)
    f.collAllBtn:SetPoint("TOP", 0, -272)
    f.collAllBtn:SetText("» All «")
    f.collAllBtn:SetScript("OnClick", function()
        ETB.Filtering:SetCollectedFilter("all")
        MF:UpdateCollectionButtons("all")
        MF:RefreshItems()
    end)
    
    f.collectedBtn = CreateFrame("Button", nil, sidebar, "UIPanelButtonTemplate")
    f.collectedBtn:SetSize((SIDEBAR_WIDTH - 24) / 2, 24)
    f.collectedBtn:SetPoint("TOPLEFT", 8, -296)
    f.collectedBtn:SetText("Collected")
    f.collectedBtn:SetScript("OnClick", function()
        ETB.Filtering:SetCollectedFilter("collected")
        MF:UpdateCollectionButtons("collected")
        MF:RefreshItems()
    end)
    
    f.notCollectedBtn = CreateFrame("Button", nil, sidebar, "UIPanelButtonTemplate")
    f.notCollectedBtn:SetSize((SIDEBAR_WIDTH - 24) / 2, 24)
    f.notCollectedBtn:SetPoint("LEFT", f.collectedBtn, "RIGHT", 4, 0)
    f.notCollectedBtn:SetText("Need")
    f.notCollectedBtn:SetScript("OnClick", function()
        ETB.Filtering:SetCollectedFilter("uncollected")
        MF:UpdateCollectionButtons("uncollected")
        MF:RefreshItems()
    end)
    
    -- Set initial button colors
    MF:UpdateCollectionButtons("all")
    
    -- Section divider 5 - Stats
    local divider5 = sidebar:CreateTexture(nil, "ARTWORK")
    divider5:SetSize(SIDEBAR_WIDTH - 16, 2)
    divider5:SetPoint("TOP", 0, -324)
    divider5:SetColorTexture(0.5, 0.5, 0.5, 0.5)
    
    -- Collection Stats
    local statsLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    statsLabel:SetPoint("TOPLEFT", 8, -330)
    statsLabel:SetText("|cffffd100Collection Stats|r")
    
    f.statsOverall = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.statsOverall:SetPoint("TOPLEFT", 8, -346)
    f.statsOverall:SetText("|cffffffffOverall: 0/0 (0%)|r")
    
    f.statsSlot = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.statsSlot:SetPoint("TOPLEFT", 8, -360)
    f.statsSlot:SetText("|cffffffffSlot: 0/0 (0%)|r")
    
    f.statsShowing = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.statsShowing:SetPoint("TOPLEFT", 8, -374)
    f.statsShowing:SetText("|cffffffffShowing: 0 items|r")
    
    -- Refresh Data Button
    f.refreshBtn = CreateFrame("Button", nil, sidebar, "UIPanelButtonTemplate")
    f.refreshBtn:SetSize(SIDEBAR_WIDTH - 16, 20)
    f.refreshBtn:SetPoint("TOP", 0, -394)
    f.refreshBtn:SetText("Refresh Data")
    f.refreshBtn:SetScript("OnClick", function()
        MF:RefreshItems()
    end)
    
    -- Set Builder embed container (below Refresh Data)
    f.setBuilderEmbedContainer = CreateFrame("Frame", nil, sidebar, "BackdropTemplate")
    f.setBuilderEmbedContainer:SetPoint("TOPLEFT", sidebar, 0, -576)
    f.setBuilderEmbedContainer:SetPoint("BOTTOMRIGHT", sidebar, 0, 8)
    f.setBuilderEmbedContainer:SetFrameLevel(sidebar:GetFrameLevel() + 10)
    f.setBuilderEmbedContainer:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 8,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    f.setBuilderEmbedContainer:Hide()
    
    -- Note: Removed Armor Slot Dropdown (use top icons instead)
    -- Note: Removed Profile Selector (still load profiles for tracking)
    
    -- Hidden dummy for armor slot label (for compatibility)
    f.armorSlotLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    f.armorSlotLabel:Hide()
    
    -- Weapon Category Selector (only shown when in weapon mode)
    f.weaponCategoryLabel = sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    f.weaponCategoryLabel:SetPoint("TOPLEFT", 10, -329)
    f.weaponCategoryLabel:SetText("|cffffd100Weapon Category|r")
    f.weaponCategoryLabel:Hide()
    
    f.weaponCategoryDropdown = ETBDropdown:Create("ETBWeaponCategoryDropdown", sidebar)
    f.weaponCategoryDropdown:SetPoint("TOPLEFT", 0, -346)
    ETBDropdown:SetWidth(f.weaponCategoryDropdown, SIDEBAR_WIDTH - 50)
    ETBDropdown:SetText(f.weaponCategoryDropdown, "Select Weapon Type")
    ETBDropdown:Initialize(f.weaponCategoryDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        local weaponCategories = {
            { id = 14, name = "1H Swords (399)" },
            { id = 13, name = "1H Axes (256)" },
            { id = 15, name = "1H Maces (390)" },
            { id = 16, name = "Daggers (452)" },
            { id = 17, name = "Fist Weapons (148)" },
            { id = 21, name = "2H Swords (212)" },
            { id = 20, name = "2H Axes (210)" },
            { id = 22, name = "2H Maces (200)" },
            { id = 23, name = "Staves (428)" },
            { id = 24, name = "Polearms (152)" },
            { id = 25, name = "Bows (165)" },
            { id = 26, name = "Guns (150)" },
            { id = 27, name = "Crossbows (87)" },
            { id = 12, name = "Wands (241)" },
            { id = 18, name = "Shields (368)" },
            { id = 19, name = "Off-Hands (263)" },
        }
        for _, cat in ipairs(weaponCategories) do
            info.text = cat.name
            info.value = cat.id
            info.func = function()
                ETBDropdown:SetText(f.weaponCategoryDropdown, cat.name)
                ETB.Filtering:SetCategory(cat.id)
                MF:UpdateSlotHighlight(cat.id)
                local catInfo = ETB.CATEGORY_INFO[cat.id]
                MF:UpdateCurrentViewingLabel(catInfo and catInfo.name or cat.name)
                MF:RefreshItems()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    f.weaponCategoryDropdown:Hide()
    
    -- Compatibility aliases
    f.weaponTypeLabel = f.weaponCategoryLabel
    f.weaponTypeDropdown = f.weaponCategoryDropdown
end

function MF:CreateTab(parent, text, x, y)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(46, 22)
    btn:SetPoint("TOPLEFT", x, y)
    
    btn.bg = btn:CreateTexture(nil, "BACKGROUND")
    btn.bg:SetAllPoints()
    btn.bg:SetColorTexture(0.2, 0.2, 0.2, 0.8)
    
    btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    btn.text:SetAllPoints()
    btn.text:SetText(text)
    
    return btn
end

function MF:UpdateCategoryTabs()
    local f = self.frame
    if not f then return end
    
    local tabs = { armor = f.armorTab, weapons = f.weaponsTab, sets = f.setsTab, setbuilder = f.setBuilderTab }
    for mode, tab in pairs(tabs) do
        if tab then
            if mode == self.categoryMode then
                tab.bg:SetColorTexture(0.1, 0.4, 0.1, 0.9)
                tab.text:SetTextColor(0, 1, 0)
            else
                tab.bg:SetColorTexture(0.2, 0.2, 0.2, 0.8)
                tab.text:SetTextColor(1, 1, 1)
            end
        end
    end
end

function MF:UpdateCollectionButtons(filter)
    local f = self.frame
    if not f then return end
    
    filter = filter or ETB.Filtering.filters.collected or "all"
    
    -- Reset all buttons to default
    if f.collAllBtn then
        f.collAllBtn:SetText(filter == "all" and "|cff00ffff» All «|r" or "» All «")
    end
    if f.collectedBtn then
        f.collectedBtn:SetText(filter == "collected" and "|cff00ff00Collected|r" or "Collected")
    end
    if f.notCollectedBtn then
        f.notCollectedBtn:SetText(filter == "uncollected" and "|cffff6666Not Collected|r" or "Not Collected")
    end
    
    -- Legacy support for old button names
    if f.allItemsBtn then
        f.allItemsBtn:SetText(filter == "all" and "|cffffffff» All «|r" or "All Items")
    end
end

function MF:UpdateWeaponTypeFilter()
    -- Delegate to new function name
    self:UpdateWeaponCategoryUI()
end

function MF:UpdateWeaponCategoryUI()
    local f = self.frame
    if not f then return end
    
    -- Show/hide appropriate category selector based on mode
    if self.categoryMode == "weapons" then
        -- Weapon category selected via icon buttons at top - no dropdown needed
        if f.weaponCategoryLabel then f.weaponCategoryLabel:Hide() end
        if f.weaponCategoryDropdown then f.weaponCategoryDropdown:Hide() end
        if f.armorSlotLabel then f.armorSlotLabel:Hide() end
        if f.armorSlotDropdown then f.armorSlotDropdown:Hide() end
        
        -- Update dropdown text to show current category
        local categoryID = ETB.Filtering.filters.category
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[categoryID]
        if catInfo and f.weaponCategoryDropdown then
            ETBDropdown:SetText(f.weaponCategoryDropdown, catInfo.name)
        end
    else
        -- Show armor slot dropdown, hide weapon category dropdown
        if f.armorSlotLabel then f.armorSlotLabel:Show() end
        if f.armorSlotDropdown then f.armorSlotDropdown:Show() end
        if f.weaponCategoryLabel then f.weaponCategoryLabel:Hide() end
        if f.weaponCategoryDropdown then f.weaponCategoryDropdown:Hide() end
        
        -- Update dropdown text to show current slot
        local categoryID = ETB.Filtering.filters.category
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[categoryID]
        if catInfo and f.armorSlotDropdown then
            ETBDropdown:SetText(f.armorSlotDropdown, catInfo.name)
        end
    end
end

-- =============================================================================
-- ITEM GRID (Uses ItemRow components)
-- =============================================================================

function MF:CreateItemGrid(f)
    local grid = CreateFrame("Frame", nil, f, "BackdropTemplate")
    grid:SetPoint("TOPLEFT", f.sidebar, "TOPRIGHT", 14, 0)
    grid:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -(PREVIEW_WIDTH + FRAME_MARGIN + 14), BOTTOM_MARGIN)
    grid:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    f.gridFrame = grid
    f.grid = grid
    grid:SetClipsChildren(true)
    
    -- Enable mouse wheel scrolling for page navigation
    grid:EnableMouseWheel(true)
    grid:SetScript("OnMouseWheel", function(self, delta)
        if delta > 0 then
            -- Scroll up = previous page
            if MF.currentPage > 1 then
                MF.currentPage = MF.currentPage - 1
                MF:LoadPageData(MF.currentPage)
                MF:UpdateDisplay()
                MF:UpdateStats()
            end
        else
            -- Scroll down = next page
            local totalPages = math.ceil(MF.totalItemCount / MF.itemsPerPage)
            if MF.currentPage < totalPages then
                MF.currentPage = MF.currentPage + 1
                MF:LoadPageData(MF.currentPage)
                MF:UpdateDisplay()
                MF:UpdateStats()
            end
        end
    end)
    
    -- Section label
    local gridLabel = grid:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    gridLabel:SetPoint("TOPLEFT", 6, -5)
    gridLabel:SetText("|cff00ffffItems|r")
    
    -- Scroll hint
    local scrollHint = grid:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    scrollHint:SetPoint("TOPRIGHT", -6, -5)
    scrollHint:SetText("|cff555555Scroll to page|r")
    
    self.itemRows = {}
    for row = 1, ROWS_PER_COLUMN do
        for col = 1, COLUMNS do
            local idx = (row - 1) * COLUMNS + col
            local itemRow = ETB.ItemRow:Acquire(grid)
            itemRow:SetHeight(ITEM_ROW_HEIGHT - 2)
            itemRow:SetPoint("TOPLEFT", 4, -18 - (row-1) * ITEM_ROW_HEIGHT)
            itemRow:SetPoint("RIGHT", grid, "RIGHT", -4, 0)
            itemRow:Hide()
            self.itemRows[idx] = itemRow
        end
    end
end

-- =============================================================================
-- LIVE PREVIEW (Right Panel with Slot Buttons + Remove Icons)
-- =============================================================================

function MF:CreateLivePreview(f)
    local preview = CreateFrame("Frame", nil, f, "BackdropTemplate")
    preview:SetWidth(PREVIEW_WIDTH)
    preview:SetPoint("TOPRIGHT", -FRAME_MARGIN, CONTENT_Y)
    preview:SetPoint("BOTTOMRIGHT", -FRAME_MARGIN, BOTTOM_MARGIN)
    preview:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    f.preview = preview
    
    local title = preview:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOP", 0, -12)
    title:SetText("|cff00ffffLive Preview|r")
    
    -- Section divider
    local previewDivider = preview:CreateTexture(nil, "ARTWORK")
    previewDivider:SetSize(PREVIEW_WIDTH - 8, 2)
    previewDivider:SetPoint("TOP", 0, -35)
    previewDivider:SetColorTexture(0.5, 0.5, 0.5, 0.5)
    
    -- Model (BIGGER: 260x420)
    f.previewModel = CreateFrame("DressUpModel", nil, preview)
    f.previewModel:SetSize(260, 420)
    f.previewModel:SetPoint("TOP", 0, -40)
    f.previewModel:SetUnit("player")
    f.previewModel:SetRotation(0)
    
    -- Mouse rotation/zoom
    f.previewModel:EnableMouse(true)
    f.previewModel:EnableMouseWheel(true)
    f.previewModel.zoomLevel = 0
    f.previewModel:SetScript("OnMouseDown", function(self, btn)
        if btn == "LeftButton" then
            self.rotating = true
            self.startX = GetCursorPosition()
        end
    end)
    f.previewModel:SetScript("OnMouseUp", function(self) self.rotating = false end)
    f.previewModel:SetScript("OnUpdate", function(self)
        if self.rotating then
            local x = GetCursorPosition()
            MF.previewRotation = (MF.previewRotation or 0) + (x - self.startX) * 0.01
            self:SetRotation(MF.previewRotation)
            self.startX = x
        end
    end)
    -- Proper zoom using camera distance
    f.previewModel:SetScript("OnMouseWheel", function(self, delta)
        self.zoomLevel = (self.zoomLevel or 0) + delta * 0.1
        self.zoomLevel = math.max(-0.5, math.min(1.5, self.zoomLevel))
        self:SetCamDistanceScale(1 - self.zoomLevel)
    end)
    
    -- Create slot buttons on left side
    f.leftSlotBtns = {}
    for i, slotName in ipairs(PREVIEW_SLOTS_LEFT) do
        local btn = self:CreateSlotButton(preview, slotName, i)
        btn:SetPoint("TOPLEFT", 5, -30 - (i-1) * 26)
        f.leftSlotBtns[slotName] = btn
    end
    
    -- Create slot buttons on right side  
    f.rightSlotBtns = {}
    for i, slotName in ipairs(PREVIEW_SLOTS_RIGHT) do
        local btn = self:CreateSlotButton(preview, slotName, i)
        btn:SetPoint("TOPRIGHT", -5, -30 - (i-1) * 26)
        f.rightSlotBtns[slotName] = btn
    end
    
    -- Hint
    local hint = preview:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    hint:SetPoint("TOP", f.previewModel, "BOTTOM", 0, -5)
    hint:SetText("|cff00ff00Drag rotate • Wheel zoom|r")
    
    -- Animation controls (hidden by default - dev tools)
    local animLabel = preview:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    animLabel:SetPoint("TOP", hint, "BOTTOM", 0, -4)
    animLabel:SetText("|cffffd100Pose:|r")
    animLabel:Hide()
    
    -- AnimationData.dbc IDs (NOT Emotes.dbc IDs)
    -- Confirmed from AnimationData.dbc
    local ANIM_PRESETS = {
        { id = 0,   label = "Stand" },      -- AnimData 0: Stand
        { id = 4,   label = "Walk" },       -- AnimData 4: Walk
        { id = 5,   label = "Run" },        -- AnimData 5: Run
        { id = 16,  label = "Attack" },     -- AnimData 16: AttackUnarmed
        { id = 25,  label = "Ready" },      -- AnimData 25: ReadyUnarmed (combat idle)
        { id = 60,  label = "Talk" },       -- AnimData 60: EmoteTalk (confirmed by Wowpedia)
        { id = 32,  label = "Cast" },       -- AnimData 32: SpellCast
        { id = 1,   label = "Death" },      -- AnimData 1: Death
    }
    
    local animBtnWidth = 48
    local animBtnHeight = 18
    local animBtnsPerRow = 4
    local animStartX = -(animBtnWidth * animBtnsPerRow + 3 * (animBtnsPerRow - 1)) / 2
    
    f.animButtons = {}
    for i, preset in ipairs(ANIM_PRESETS) do
        local row = math.floor((i - 1) / animBtnsPerRow)
        local col = (i - 1) % animBtnsPerRow
        
        local animBtn = CreateFrame("Button", nil, preview, "UIPanelButtonTemplate")
        animBtn:SetSize(animBtnWidth, animBtnHeight)
        animBtn:SetPoint("TOP", animLabel, "BOTTOM", animStartX + col * (animBtnWidth + 3) + animBtnWidth / 2, -(row * (animBtnHeight + 2) + 2))
        animBtn:SetText(preset.label)
        animBtn:GetFontString():SetFont(animBtn:GetFontString():GetFont(), 10)
        
        local animID = preset.id
        animBtn:SetScript("OnClick", function()
            for _, btn in ipairs(f.animButtons) do
                btn:GetFontString():SetTextColor(1, 0.82, 0)
            end
            animBtn:GetFontString():SetTextColor(0, 1, 0.5)
            f.previewModel:SetAnimation(animID)
            if f.animIDLabel then
                f.animIDLabel:SetText("|cff00ff00ID: " .. animID .. "|r")
            end
            f._currentAnimID = animID
        end)
        animBtn:Hide()
        f.animButtons[i] = animBtn
    end
    
    -- Animation Tester (hidden - dev tool)
    local presetRows = math.ceil(#ANIM_PRESETS / animBtnsPerRow)
    local testerY = -(presetRows * (animBtnHeight + 2) + 6)
    
    f._currentAnimID = 0
    
    local testerRow = CreateFrame("Frame", nil, preview)
    testerRow:SetSize(PREVIEW_WIDTH - 20, 20)
    testerRow:SetPoint("TOP", animLabel, "BOTTOM", 0, testerY)
    testerRow:Hide()
    
    local minusBtn = CreateFrame("Button", nil, testerRow, "UIPanelButtonTemplate")
    minusBtn:SetSize(22, 18)
    minusBtn:SetPoint("LEFT", testerRow, "CENTER", -55, 0)
    minusBtn:SetText("-")
    minusBtn:SetScript("OnClick", function()
        f._currentAnimID = max(0, (f._currentAnimID or 0) - 1)
        f.previewModel:SetAnimation(f._currentAnimID)
        f.animIDLabel:SetText("|cff00ff00ID: " .. f._currentAnimID .. "|r")
        for _, btn in ipairs(f.animButtons) do
            btn:GetFontString():SetTextColor(1, 0.82, 0)
        end
    end)
    
    f.animIDLabel = testerRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.animIDLabel:SetPoint("CENTER", testerRow, "CENTER", 0, 0)
    f.animIDLabel:SetText("|cff00ff00ID: 0|r")
    f.animIDLabel:SetWidth(50)
    f.animIDLabel:SetJustifyH("CENTER")
    
    local plusBtn = CreateFrame("Button", nil, testerRow, "UIPanelButtonTemplate")
    plusBtn:SetSize(22, 18)
    plusBtn:SetPoint("LEFT", testerRow, "CENTER", 33, 0)
    plusBtn:SetText("+")
    plusBtn:SetScript("OnClick", function()
        f._currentAnimID = min(500, (f._currentAnimID or 0) + 1)
        f.previewModel:SetAnimation(f._currentAnimID)
        f.animIDLabel:SetText("|cff00ff00ID: " .. f._currentAnimID .. "|r")
        for _, btn in ipairs(f.animButtons) do
            btn:GetFontString():SetTextColor(1, 0.82, 0)
        end
    end)
    
    local plus10Btn = CreateFrame("Button", nil, testerRow, "UIPanelButtonTemplate")
    plus10Btn:SetSize(28, 18)
    plus10Btn:SetPoint("LEFT", plusBtn, "RIGHT", 2, 0)
    plus10Btn:SetText("+10")
    plus10Btn:GetFontString():SetFont(plus10Btn:GetFontString():GetFont(), 9)
    plus10Btn:SetScript("OnClick", function()
        f._currentAnimID = min(500, (f._currentAnimID or 0) + 10)
        f.previewModel:SetAnimation(f._currentAnimID)
        f.animIDLabel:SetText("|cff00ff00ID: " .. f._currentAnimID .. "|r")
        for _, btn in ipairs(f.animButtons) do
            btn:GetFontString():SetTextColor(1, 0.82, 0)
        end
    end)
    
    f.previewItemName = preview:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    f.previewItemName:SetPoint("TOP", hint, "BOTTOM", 0, -(math.ceil(#ANIM_PRESETS / animBtnsPerRow) * (animBtnHeight + 2) + 30))
    f.previewItemName:SetWidth(260)
    
    -- Buttons (below model + hint text)
    local btnY = -480
    
    f.undressBtn = CreateFrame("Button", nil, preview, "UIPanelButtonTemplate")
    f.undressBtn:SetSize(105, 22)
    f.undressBtn:SetPoint("TOPLEFT", 10, btnY)
    f.undressBtn:SetText("Undress")
    f.undressBtn:SetScript("OnClick", function()
        f.previewModel:Undress()
        wipe(MF.livePreviewSlots)
        MF:UpdateSlotButtons()
    end)
    
    f.myGearBtn = CreateFrame("Button", nil, preview, "UIPanelButtonTemplate")
    f.myGearBtn:SetSize(105, 22)
    f.myGearBtn:SetPoint("TOPRIGHT", -10, btnY)
    f.myGearBtn:SetText("My Gear")
    f.myGearBtn:SetScript("OnClick", function()
        -- Avoid SetUnit which causes graphics reset - just dress
        f.previewModel:Dress()
        wipe(MF.livePreviewSlots)
    end)
    
    f.clearBtn = CreateFrame("Button", nil, preview, "UIPanelButtonTemplate")
    f.clearBtn:SetSize(PREVIEW_WIDTH - 30, 22)
    f.clearBtn:SetPoint("TOP", 0, btnY - 28)
    f.clearBtn:SetText("Clear All")
    f.clearBtn:SetScript("OnClick", function()
        f.previewModel:Undress()
        MF.previewRotation = 0
        f.previewModel:SetRotation(0)
        f.previewModel:SetPosition(0, 0, 0)
        f.previewItemName:SetText("")
        wipe(MF.livePreviewSlots)
        MF:UpdateSlotButtons()
    end)
    
    -- Random Outfit button
    f.randomOutfitBtn = CreateFrame("Button", nil, preview, "UIPanelButtonTemplate")
    f.randomOutfitBtn:SetSize(PREVIEW_WIDTH - 30, 22)
    f.randomOutfitBtn:SetPoint("TOP", 0, btnY - 56)
    f.randomOutfitBtn:SetText("|cffffd100Random Outfit|r")
    f.randomOutfitBtn:SetScript("OnClick", function()
        MF:GenerateRandomOutfit()
    end)
    
    -- See Sources button
    f.seeSourcesBtn = CreateFrame("Button", nil, preview, "UIPanelButtonTemplate")
    f.seeSourcesBtn:SetSize(PREVIEW_WIDTH - 30, 22)
    f.seeSourcesBtn:SetPoint("TOP", 0, btnY - 84)
    f.seeSourcesBtn:SetText("|cff00ffffSee Sources|r")
    f.seeSourcesBtn:SetScript("OnClick", function()
        if ETB.SourcesSummaryPanel then
            ETB.SourcesSummaryPanel:Show(MF.livePreviewSlots)
        end
    end)
    f.seeSourcesBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText("See Sources")
        GameTooltip:AddLine("View all sources for your outfit items", 0.8, 0.8, 0.8)
        GameTooltip:AddLine("grouped by location for efficient farming", 0.8, 0.8, 0.8)
        GameTooltip:Show()
    end)
    f.seeSourcesBtn:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
    
    -- Save Outfit button
    f.saveOutfitBtn = CreateFrame("Button", nil, preview, "UIPanelButtonTemplate")
    f.saveOutfitBtn:SetSize(PREVIEW_WIDTH - 30, 22)
    f.saveOutfitBtn:SetPoint("TOP", 0, btnY - 112)
    f.saveOutfitBtn:SetText("|cffffd100Save Outfit|r")
    f.saveOutfitBtn:SetScript("OnClick", function()
        -- Count items in live preview
        local count = 0
        for _ in pairs(MF.livePreviewSlots) do count = count + 1 end
        if count == 0 then
            ETB:Print("|cffff4444No items in preview to save.|r")
            return
        end
        
        -- Show name dialog (defined at load time to avoid taint)
        StaticPopup_Show("ETB_SAVE_PREVIEW_OUTFIT")
    end)
    f.saveOutfitBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText("Save Outfit")
        local count = 0
        for _ in pairs(MF.livePreviewSlots) do count = count + 1 end
        GameTooltip:AddLine("Save the current preview as an outfit", 0.8, 0.8, 0.8)
        GameTooltip:AddLine(count .. " pieces in preview", 1, 0.82, 0)
        GameTooltip:AddLine("Outfit will appear in TransmogUI", 0.5, 0.5, 0.5)
        GameTooltip:Show()
    end)
    f.saveOutfitBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
end

-- Static popup defined at load time to avoid taint (never define inside callbacks)
StaticPopupDialogs["ETB_SAVE_PREVIEW_OUTFIT"] = {
    text = "Save outfit name:",
    button1 = "Save",
    button2 = "Cancel",
    hasEditBox = true,
    maxLetters = 30,
    OnAccept = function(self)
        local eb = self.editBox or self.EditBox
        local name = eb and eb:GetText() or "Preview Outfit"
        if name == "" then name = "Preview Outfit" end
        local items = {}
        local pieceCount = 0
        local firstIcon = nil
        for slotName, item in pairs(MF.livePreviewSlots) do
            items[slotName] = { itemID = item.itemID, name = item.name or "Unknown", icon = item.icon, sourceID = item.sourceID }
            pieceCount = pieceCount + 1
            if not firstIcon and item.icon then firstIcon = item.icon end
        end
        ETBTUI_SavedOutfits = ETBTUI_SavedOutfits or {}
        local id = "preview_" .. time() .. "_" .. math.random(1000, 9999)
        ETBTUI_SavedOutfits[id] = { id = id, name = name, icon = firstIcon or "Interface\\Icons\\INV_Chest_Cloth_17", items = items, created = time(), pieceCount = pieceCount }
        NS.ETB:Print(string.format("|cff00ff00Saved outfit:|r %s (%d pieces)", name, pieceCount))
        if NS.ETB.TransmogUI and NS.ETB.TransmogUI.RefreshOutfits then NS.ETB.TransmogUI:RefreshOutfits() end
    end,
    OnShow = function(self)
        local eb = self.editBox or self.EditBox
        if eb then eb:SetText("Preview Outfit") end
    end,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

-- Slot name to category ID mapping
local SLOT_TO_CATEGORY_MAP = {
    Head = 1,
    Shoulder = 2,
    Back = 3,
    Chest = 4,
    Shirt = 5,
    Tabard = 6,
    Wrist = 7,
    Hands = 8,
    Waist = 9,
    Legs = 10,
    Feet = 11,
    -- MainHand and OffHand handled specially
}

-- Get first usable weapon category for player's class
-- mainHand: true for main hand weapons, false for off-hand
local function GetFirstUsableWeaponCategory(forOffHand)
    local usableWeapons = ETB.BlizzardData and ETB.BlizzardData:GetPlayerWeaponTypes() or {}
    
    if forOffHand then
        -- Off-hand categories: 18 (Shields), 19 (Off-Hands/Holdables)
        -- Check shields first
        if usableWeapons["SHIELD"] then
            return 18  -- Shields
        end
        -- Then holdables (most classes can use these)
        if usableWeapons["HOLDABLE"] then
            return 19  -- Off-Hands
        end
        -- Fallback to shields
        return 18
    else
        -- Main hand weapon categories in preferred order for browsing
        -- Order: 1H weapons first (more variety), then 2H, then ranged
        local categoryOrder = {
            -- One-handed melee (most common, good variety)
            { cat = 14, type = "SWORD" },      -- 1H Swords
            { cat = 13, type = "AXE" },        -- 1H Axes
            { cat = 15, type = "MACE" },       -- 1H Maces
            { cat = 16, type = "DAGGER" },     -- Daggers
            { cat = 17, type = "FIST" },       -- Fist Weapons
            -- Two-handed melee
            { cat = 21, type = "SWORD" },      -- 2H Swords
            { cat = 20, type = "AXE" },        -- 2H Axes
            { cat = 22, type = "MACE" },       -- 2H Maces
            { cat = 23, type = "STAFF" },      -- Staves
            { cat = 24, type = "POLEARM" },    -- Polearms
            -- Ranged
            { cat = 25, type = "BOW" },        -- Bows
            { cat = 26, type = "GUN" },        -- Guns
            { cat = 27, type = "CROSSBOW" },   -- Crossbows
            { cat = 12, type = "WAND" },       -- Wands
        }
        
        for _, entry in ipairs(categoryOrder) do
            if usableWeapons[entry.type] then
                return entry.cat
            end
        end
        
        -- Fallback to first weapon category
        return 14  -- 1H Swords
    end
end

function MF:CreateSlotButton(parent, slotName, index)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(55, 22)
    
    btn.bg = btn:CreateTexture(nil, "BACKGROUND")
    btn.bg:SetAllPoints()
    btn.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)
    
    btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    btn.text:SetPoint("LEFT", 3, 0)
    btn.text:SetText(slotName:sub(1, 6))
    btn.text:SetTextColor(0.6, 0.6, 0.6)
    
    -- Remove/Hide icon (X button)
    btn.removeBtn = CreateFrame("Button", nil, btn)
    btn.removeBtn:SetSize(14, 14)
    btn.removeBtn:SetPoint("RIGHT", -2, 0)
    btn.removeBtn:SetNormalTexture("Interface\\Buttons\\UI-StopButton")
    btn.removeBtn:SetHighlightTexture("Interface\\Buttons\\UI-StopButton", "ADD")
    btn.removeBtn:Hide()
    btn.removeBtn:SetScript("OnClick", function()
        -- Remove this slot from preview
        MF.livePreviewSlots[slotName] = nil
        MF:RebuildLivePreview()
        btn.text:SetTextColor(0.6, 0.6, 0.6)
        btn.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)
        btn.removeBtn:Hide()
    end)
    
    -- Click empty slot to filter to that category; Right-click filled slot to see sources
    btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    btn:SetScript("OnClick", function(self, button)
        if button == "RightButton" then
            -- Right-click: Open SourcesModal for the item in this slot
            local item = MF.livePreviewSlots[slotName]
            if item and ETB.SourcesModal then
                ETB.SourcesModal:Show(item)
            end
            return
        end
        
        -- Left-click: Only act if slot is empty
        if MF.livePreviewSlots[slotName] then
            return  -- Slot is filled, do nothing (use X button to remove)
        end
        
        local categoryID
        
        if slotName == "MainHand" then
            -- Get first usable weapon category for main hand
            categoryID = GetFirstUsableWeaponCategory(false)
        elseif slotName == "OffHand" then
            -- Get first usable off-hand category
            categoryID = GetFirstUsableWeaponCategory(true)
        else
            -- Armor slot - direct mapping
            categoryID = SLOT_TO_CATEGORY_MAP[slotName]
        end
        
        if categoryID and ETB.Filtering then
            ETB.Filtering:SetCategory(categoryID)
            -- Refresh the item list
            if MF.RefreshItems then
                MF:RefreshItems()
            end
            -- Update UI highlighting
            if MF.UpdateSlotHighlight then
                MF:UpdateSlotHighlight(categoryID)
            end
        end
    end)
    
    btn:SetScript("OnEnter", function(self)
        self.bg:SetColorTexture(0.25, 0.25, 0.25, 0.8)
        GameTooltip:SetOwner(self, "ANCHOR_LEFT")
        
        if MF.livePreviewSlots[slotName] then
            local item = MF.livePreviewSlots[slotName]
            GameTooltip:SetText(item.name or "Unknown")
            GameTooltip:AddLine("Click X to remove", 0.7, 0.7, 0.7)
            GameTooltip:AddLine("Right-click for source info", 0, 1, 1)
        else
            -- Empty slot - show hint to click to filter
            GameTooltip:SetText(slotName .. " (Empty)")
            GameTooltip:AddLine("Click to browse " .. slotName .. " items", 0, 1, 1)
            if slotName == "MainHand" or slotName == "OffHand" then
                GameTooltip:AddLine("(Shows weapons your class can use)", 0.7, 0.7, 0.7)
            end
        end
        GameTooltip:Show()
    end)
    btn:SetScript("OnLeave", function(self)
        if not MF.livePreviewSlots[slotName] then
            self.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)
        else
            self.bg:SetColorTexture(0.1, 0.3, 0.1, 0.8)
        end
        GameTooltip:Hide()
    end)
    
    btn.slotName = slotName
    return btn
end

function MF:UpdateSlotButtons()
    if not self.frame then return end
    
    local allBtns = {}
    for name, btn in pairs(self.frame.leftSlotBtns or {}) do allBtns[name] = btn end
    for name, btn in pairs(self.frame.rightSlotBtns or {}) do allBtns[name] = btn end
    
    for slotName, btn in pairs(allBtns) do
        if self.livePreviewSlots[slotName] then
            btn.text:SetTextColor(0, 1, 0)
            btn.bg:SetColorTexture(0.1, 0.3, 0.1, 0.8)
            btn.removeBtn:Show()
        else
            btn.text:SetTextColor(0.6, 0.6, 0.6)
            btn.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)
            btn.removeBtn:Hide()
        end
    end
end

function MF:RebuildLivePreview()
    if not self.frame then return end
    
    self.frame.previewModel:Undress()
    for slotName, item in pairs(self.livePreviewSlots) do
        if item and item.itemID then
            self.frame.previewModel:TryOn("item:" .. item.itemID)
        end
    end
    self:UpdateSlotButtons()
end

-- =============================================================================
-- BOTTOM BAR (Pagination with refresh + Memory display)
-- =============================================================================

function MF:CreateBottomBar(f)
    local bar = CreateFrame("Frame", nil, f, "BackdropTemplate")
    bar:SetPoint("BOTTOMLEFT", FRAME_MARGIN, FRAME_MARGIN)
    bar:SetPoint("BOTTOMRIGHT", -FRAME_MARGIN, FRAME_MARGIN)
    bar:SetHeight(BOTTOMBAR_HEIGHT)
    bar:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    f.bottomBar = bar
    
    -- Previous button (left)
    f.prevBtn = CreateFrame("Button", nil, bar, "UIPanelButtonTemplate")
    f.prevBtn:SetSize(90, 26)
    f.prevBtn:SetPoint("LEFT", 8, 0)
    f.prevBtn:SetText("« Previous")
    f.prevBtn:SetScript("OnClick", function()
        if MF.currentPage > 1 then
            MF.currentPage = MF.currentPage - 1
            MF:UpdateDisplayWithRefresh()
        end
    end)
    
    -- Page text (center-left, inline)
    f.pageText = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.pageText:SetPoint("LEFT", f.prevBtn, "RIGHT", 8, 0)
    
    f.itemCountText = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.itemCountText:SetPoint("LEFT", f.pageText, "RIGHT", 8, 0)
    
    -- Next button
    f.nextBtn = CreateFrame("Button", nil, bar, "UIPanelButtonTemplate")
    f.nextBtn:SetSize(90, 26)
    f.nextBtn:SetPoint("LEFT", f.itemCountText, "RIGHT", 8, 0)
    f.nextBtn:SetText("Next »")
    f.nextBtn:SetScript("OnClick", function()
        local totalPages = math.ceil(MF.totalItemCount / MF.itemsPerPage)
        if MF.currentPage < totalPages then
            MF.currentPage = MF.currentPage + 1
            MF:UpdateDisplayWithRefresh()
        end
    end)
    
    -- Memory display (far right)
    f.memoryText = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.memoryText:SetPoint("RIGHT", bar, "RIGHT", -8, 0)
    f.memoryText:SetText("|cff555555Mem: 0 KB|r")
    
    -- Collection stats (right of next button)
    f.collectionStats = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.collectionStats:SetPoint("RIGHT", f.memoryText, "LEFT", -12, 0)
    f.collectionStats:SetText("")
end

-- =============================================================================
-- ADD TO LIVE PREVIEW (builds outfit)
-- =============================================================================

function MF:AddToLivePreview(item)
    if not self.frame or not item or not item.itemID then return end
    
    -- Determine slot name from item
    local slotName = self:GetSlotNameForItem(item)
    
    -- Store in slot
    if slotName then
        self.livePreviewSlots[slotName] = item
    end
    
    -- Add to model (don't undress)
    self.frame.previewModel:TryOn("item:" .. item.itemID)
    
    local color = ETB:GetQualityColor(item.quality)
    self.frame.previewItemName:SetText(color .. (item.name or "Unknown") .. "|r")
    
    self:UpdateSlotButtons()
end

function MF:GetSlotNameForItem(item)
    -- Map category ID to slot name
    local categoryID = ETB.Filtering.filters.category
    local catInfo = ETB.CATEGORY_INFO[categoryID]
    if catInfo then
        return catInfo.name
    end
    return "Unknown"
end

-- =============================================================================
-- SETS VIEW
-- =============================================================================

function MF:ShowSetsView()
    if not self.frame then return end
    
    -- Hide Set Builder panel when showing Sets
    MF:HideSetBuilderView()
    
    -- Hide the regular items grid
    if self.frame.gridFrame then
        self.frame.gridFrame:Hide()
    end
    
    -- Hide live preview when in sets mode
    if self.frame.preview then
        self.frame.preview:Hide()
    end
    
    -- Hide Set Builder container
    if self.setBuilderContainer then
        self.setBuilderContainer:Hide()
    end
    
    -- Show the sets UI
    if ETB.SetsUI then
        if not self.setsContainer then
            self.setsContainer = CreateFrame("Frame", nil, self.frame)
            self.setsContainer:SetPoint("TOPLEFT", self.frame.sidebar, "TOPRIGHT", 8, 0)
            self.setsContainer:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -FRAME_MARGIN, BOTTOM_MARGIN)
        end
        self.setsContainer:Show()
        ETB.SetsUI:Show(self.setsContainer)
    else
        ETB:Print("|cffff0000SetsUI module not loaded!|r")
    end
    
    self:UpdateCurrentViewingLabel("Transmog Sets")
end

function MF:ShowSetBuilderView()
    if not self.frame then return end
    
    -- Hide sets UI and item grid
    if ETB.SetsUI then ETB.SetsUI:Hide() end
    if self.setsContainer then self.setsContainer:Hide() end
    if self.frame.gridFrame then self.frame.gridFrame:Hide() end
    if self.frame.preview then self.frame.preview:Hide() end
    
    -- Create and show Set Builder container (same area as Sets)
    if not self.setBuilderContainer then
        self.setBuilderContainer = CreateFrame("Frame", nil, self.frame)
        self.setBuilderContainer:SetPoint("TOPLEFT", self.frame.sidebar, "TOPRIGHT", 8, 0)
        self.setBuilderContainer:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -FRAME_MARGIN, BOTTOM_MARGIN)
    end
    self.setBuilderContainer:Show()
    
    if ETB.SetBuilder and ETB.SetBuilder.ShowAsPanel then
        ETB.SetBuilder:ShowAsPanel(self.setBuilderContainer)
    end
    
    self:UpdateCurrentViewingLabel("Set Builder")
end

function MF:HideSetBuilderView()
    if self.setBuilderContainer then
        self.setBuilderContainer:Hide()
    end
    if ETB.SetBuilder and ETB.SetBuilder.HidePanel then
        ETB.SetBuilder:HidePanel()
    end
end

function MF:ShowSetBuilderEmbedded()
    local f = self.frame
    if not f or not f.setBuilderEmbedContainer then return end
    f.setBuilderEmbedContainer:Show()
    if ETB.SetBuilder and ETB.SetBuilder.ShowEmbedded then
        ETB.SetBuilder:ShowEmbedded(f.setBuilderEmbedContainer)
    end
end

function MF:HideSetBuilderEmbedded()
    local f = self.frame
    if not f or not f.setBuilderEmbedContainer then return end
    f.setBuilderEmbedContainer:Hide()
    if ETB.SetBuilder and ETB.SetBuilder.HideEmbedded then
        ETB.SetBuilder:HideEmbedded()
    end
end

function MF:IsSetBuilderEmbeddedShown()
    local f = self.frame
    return f and f.setBuilderEmbedContainer and f.setBuilderEmbedContainer:IsShown()
end

function MF:HideSetsView()
    if ETB.SetsUI then
        ETB.SetsUI:Hide()
    end
    
    if self.setsContainer then
        self.setsContainer:Hide()
    end
    
    MF:HideSetBuilderView()
    MF:HideSetBuilderEmbedded()
    
    if self.frame and self.frame.gridFrame then
        self.frame.gridFrame:Show()
    end
    
    if self.frame and self.frame.preview then
        self.frame.preview:Show()
    end
end

-- =============================================================================
-- REFRESH & DISPLAY
-- =============================================================================

function MF:RefreshItems()
    if not self.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 self._filteredItems then
        wipe(self._filteredItems)
        self._filteredItems = nil
    end
    self.items = {}
    self.lastPageLoaded = 0
    
    -- Clear all ItemRow data
    self: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.
    self.currentPage = 1
    self._filteredItems = ETB.Filtering:GetItems({ limit = 100000 })
    self.totalItemCount = #self._filteredItems
    
    -- Slice first page
    local startIdx = 1
    local endIdx = self.itemsPerPage
    
    self.items = {}
    for i = startIdx, math.min(endIdx, #self._filteredItems) do
        if self._filteredItems[i] then
            local item = self._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
            self.items[#self.items + 1] = item
        end
    end
    
    self.lastPageLoaded = 1
    collectgarbage("step", 100)
    
    self:UpdateDisplay()
    self:UpdateStats()
end

function MF:LoadPageData(pageNum)
    if not self.frame then return end
    if self.lastPageLoaded == pageNum and #self.items > 0 then
        return
    end
    
    self.items = {}
    self.lastPageLoaded = pageNum
    
    -- Use cached filtered results if available, otherwise reload
    if not self._filteredItems or #self._filteredItems == 0 then
        self._filteredItems = ETB.Filtering:GetItems({ limit = 100000 })
        self.totalItemCount = #self._filteredItems
    end
    
    local startIdx = (pageNum - 1) * self.itemsPerPage + 1
    local endIdx = startIdx + self.itemsPerPage - 1
    
    for i = startIdx, math.min(endIdx, #self._filteredItems) do
        if self._filteredItems[i] then
            local item = self._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
            self.items[#self.items + 1] = item
        end
    end
    
    self:UpdateDisplay()
    
    if pageNum == 1 then
        self:PreloadFirstPageItemInfo()
    else
        self:RequestItemInfoAsync()
    end
end

function MF:PreloadFirstPageItemInfo()
    -- 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(self.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
                self.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
        self: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(self.items) do
            if item and item.itemID and ETB.BlizzardData.itemCache then
                local cached = ETB.BlizzardData.itemCache:Get(item.itemID)
                if cached then
                    self.items[i] = cached
                end
            end
        end
        self:UpdateDisplay()
    end
end

function MF:RequestItemInfoAsync()
    -- Request item info async for all items on the current page
    if not ETB.BlizzardData then return end
    
    for _, item in ipairs(self.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(self.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
        self:UpdateDisplay()
    end
end

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

function MF:UpdateDisplayWithRefresh()
    -- Force clear old data first
    self:ClearAllItemRows()
    
    -- Load new page data
    self:LoadPageData(self.currentPage)
    
    -- Display is already updated in LoadPageData if items were updated
    -- Just ensure display is current
    self:UpdateDisplay()
end

function MF:UpdateDisplay()
    if not self.frame then return end
    
    local startIdx = (self.currentPage - 1) * self.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(self.items) do
            if item and item.itemID then
                local cached = ETB.BlizzardData.itemCache:Get(item.itemID)
                if cached then
                    self.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(self.itemRows) do
        local item = self.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(self.totalItemCount / self.itemsPerPage))
    self.frame.pageText:SetText(string.format("Page %d / %d", self.currentPage, totalPages))
    self.frame.itemCountText:SetText(string.format("%d Appearances", self.totalItemCount))
    self.frame.prevBtn:SetEnabled(self.currentPage > 1)
    self.frame.nextBtn:SetEnabled(self.currentPage < totalPages)
    
    -- Collection stats
    if self.frame.collectionStats then
        local collected = 0
        for _, item in ipairs(self.items) do
            if item and item.isCollected then collected = collected + 1 end
        end
        local shown = #self.items
        if shown > 0 then
            local pct = math.floor(100 * collected / self.totalItemCount + 0.5)
            self.frame.collectionStats:SetText(string.format(
                "|cff00ff00%d|r|cff888888 / %d collected (%d%%)|r", collected, self.totalItemCount, pct))
        else
            self.frame.collectionStats:SetText("")
        end
    end
end

function MF:UpdatePendingItemsForCurrentPage()
    -- 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(self.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 MF:UpdateChangedRows()
    -- Only update rows whose items have changed, not all rows
    if not self.frame then return end
    
    local startIdx = (self.currentPage - 1) * self.itemsPerPage + 1
    
    for i, row in ipairs(self.itemRows) do
        local item = self.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

function MF:UpdateStats()
    if not self.frame then return end
    
    -- Overall stats
    local allStats = ETB.BlizzardData:GetAllStats()
    local overallElement = self.frame.statsOverall or self.frame.overallStats
    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 = self.frame.statsSlot or self.frame.currentSlotStats
    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
    local showingElement = self.frame.statsShowing or self.frame.currentViewStats
    if showingElement then
        showingElement:SetText(string.format("Showing: %d items", self.totalItemCount))
    end
    
    -- Update the "Currently Viewing" label too
    self:UpdateCurrentViewingLabel(catInfo and catInfo.name or "Unknown")
end

-- =============================================================================
-- SHOW/HIDE/TOGGLE
-- =============================================================================

function MF:Show()
    if not self.frame then self:Create() end
    
    if not ETB.BlizzardData.initialized then
        ETB.BlizzardData:Initialize()
    end
    
    -- Ensure Sets are initialized
    if ETB.Sets and not ETB.Sets.INITIALIZED then
        ETB.Sets:Initialize()
    end
    
    self:UpdateCategoryTabs()
    self:UpdateCollectionButtons()
    self:UpdateSlotHighlight(ETB.Filtering.filters.category)
    self:UpdateWeaponTypeFilter()  -- Show/hide weapon type filter based on category
    
    -- Initial slot label
    local catInfo = ETB.CATEGORY_INFO[ETB.Filtering.filters.category]
    self:UpdateCurrentViewingLabel(catInfo and catInfo.name or "Head")
    
    self.frame:Show()
    self:RefreshItems()
    
    -- Periodic update for item info loading AND memory display
    -- Reduced frequency and limited processing per tick to improve performance
    if not self.updateTicker then
        self.updateTicker = C_Timer.NewTicker(0.5, function()  -- More frequent for faster name loading
            if self.frame and self.frame:IsShown() then
                -- Only update items for current page to reduce work
                local updated = self:UpdatePendingItemsForCurrentPage()
                
                if updated then
                    -- Refresh item references from cache
                    if ETB.BlizzardData and ETB.BlizzardData.itemCache then
                        for i, item in ipairs(self.items) do
                            if item and item.itemID then
                                local cached = ETB.BlizzardData.itemCache:Get(item.itemID)
                                if cached then
                                    self.items[i] = cached
                                end
                            end
                        end
                    end
                    -- Update all rows to show new names
                    self:UpdateDisplay()
                end
                
                -- Update memory display (less frequently)
                if not self.memoryUpdateCounter then self.memoryUpdateCounter = 0 end
                self.memoryUpdateCounter = self.memoryUpdateCounter + 1
                if self.memoryUpdateCounter >= 6 then  -- Update memory every 3 seconds (6 * 0.5s)
                    self:UpdateMemoryDisplay()
                    self.memoryUpdateCounter = 0
                end
            end
        end)
    end
end

function MF:UpdateMemoryDisplay()
    if not self.frame or not self.frame.memoryText then return end
    local mem = ETB:GetMemoryUsage()
    local formatted = ETB:FormatMemory(mem)
    local color = mem > 50000 and "|cffff6666" or mem > 20000 and "|cffffff00" or "|cff00ff00"
    self.frame.memoryText:SetText(color .. "Mem: " .. formatted .. "|r")
end

function MF:Hide()
    if self.frame then self.frame:Hide() end
    if self.updateTicker then
        self.updateTicker:Cancel()
        self.updateTicker = nil
    end
    -- Also hide the hover preview popup
    if ETB.ModelPreview then
        ETB.ModelPreview:Hide()
    end
end

function MF:Toggle()
    if self.frame and self.frame:IsShown() then self:Hide() else self:Show() end
end

function MF:IsShown()
    return self.frame and self.frame:IsShown()
end

-- =============================================================================
-- HOOKS: Click adds to Live Preview, Hover shows floating ModelPreview
-- =============================================================================

ETB.EventBus:Register("ETB_INITIALIZED", function()
    -- Override ItemRow click to add to Live Preview
    local origOnClick = ETB.ItemRow.OnClick
    ETB.ItemRow.OnClick = function(self, frame, button)
        local item = frame.itemData
        if not item then return end
        
        if button == "LeftButton" then
            if IsShiftKeyDown() then
                -- Shift+Click: Link in chat
                if item.itemID then
                    local _, link = GetItemInfo(item.itemID)
                    if link then ChatEdit_InsertLink(link) end
                end
            elseif IsControlKeyDown() then
                -- Ctrl+Click: Dressing room
                if item.itemID and DressUpItemLink then
                    DressUpItemLink("item:" .. item.itemID)
                end
            elseif IsAltKeyDown() then
                -- Alt+Click: Toggle favorite
                if ETB.FavoritesManager then
                    ETB.FavoritesManager:ToggleFavorite(item.itemID)
                    ETB.ItemRow:Update(frame, item, frame.index)
                end
            else
                -- Left-Click: Add to LIVE PREVIEW
                MF:AddToLivePreview(item)
            end
        elseif button == "RightButton" then
            if ETB.SourcesModal then
                ETB.SourcesModal:Show(item)
            end
        end
    end
    
    -- Hover triggers floating ModelPreview popup (separate from Live Preview)
    -- This is already handled by ItemRow.OnEnter which calls ETB.ModelPreview:PreviewItem()
    -- when ETB.Settings:Get("preview.showOnHover") is true
    
end, ETB.EventBus.Priority.LATE)

-- =============================================================================
-- RANDOM OUTFIT GENERATOR
-- =============================================================================

function MF:GenerateRandomOutfit()
    local f = self.frame
    if not f or not f.previewModel then return end
    
    -- Get player's armor type
    local _, playerClass = UnitClass("player")
    local armorTypes = {
        WARRIOR = "PLATE", PALADIN = "PLATE", DEATHKNIGHT = "PLATE",
        HUNTER = "MAIL", SHAMAN = "MAIL",
        ROGUE = "LEATHER", DRUID = "LEATHER", MONK = "LEATHER",
        MAGE = "CLOTH", WARLOCK = "CLOTH", PRIEST = "CLOTH",
    }
    local playerArmorType = armorTypes[playerClass] or "LEATHER"
    
    -- Numeric subclass IDs for armor type comparison (GetItemInfoInstant returns these)
    local ARMOR_SUBCLASS = { CLOTH = 1, LEATHER = 2, MAIL = 3, PLATE = 4 }
    local playerArmorSubclass = ARMOR_SUBCLASS[playerArmorType] or 2
    
    -- Category to preview slot name (matches PREVIEW_SLOTS_LEFT / PREVIEW_SLOTS_RIGHT)
    local catToSlotName = {
        [1] = "Head", [2] = "Shoulder", [3] = "Back", [4] = "Chest", [5] = "Shirt", [6] = "Tabard",
        [7] = "Wrist", [8] = "Hands", [9] = "Waist", [10] = "Legs", [11] = "Feet",
    }
    
    -- All transmog slots (armor + shirt + tabard)
    local slots = {
        {cat = 1, invSlot = 1},   -- Head
        {cat = 2, invSlot = 3},   -- Shoulder
        {cat = 3, invSlot = 15},  -- Back (cloak)
        {cat = 4, invSlot = 5},   -- Chest
        {cat = 5, invSlot = 4},   -- Shirt
        {cat = 6, invSlot = 19},  -- Tabard
        {cat = 7, invSlot = 9},   -- Wrist
        {cat = 8, invSlot = 10},  -- Hands
        {cat = 9, invSlot = 6},   -- Waist
        {cat = 10, invSlot = 7},  -- Legs
        {cat = 11, invSlot = 8},  -- Feet
    }
    
    -- Undress first
    f.previewModel:Undress()
    wipe(MF.livePreviewSlots)
    
    local itemsApplied = 0
    local function addToSlot(slotName, itemID)
        if not slotName or not itemID then return end
        local name, _, quality, _, _, _, _, _, _, icon = GetItemInfo(itemID)
        MF.livePreviewSlots[slotName] = { itemID = itemID, name = name, icon = icon, quality = quality or 1 }
    end
    
    for _, slotInfo in ipairs(slots) do
        local appearances = {}
        
        if C_TransmogCollection and C_TransmogCollection.GetCategoryAppearances then
            local catAppearances = C_TransmogCollection.GetCategoryAppearances(slotInfo.cat)
            if catAppearances then
                for _, app in ipairs(catAppearances) do
                    if app.isCollected then
                        local sources = C_TransmogCollection.GetAppearanceSources(app.visualID)
                        if sources and sources[1] then
                            local source = sources[1]
                            local itemID = source.itemID or source.itemId
                            if itemID then
                                local _, _, _, equipLoc, _, classID, subclassID = GetItemInfoInstant(itemID)
                                -- Back(3), Shirt(5), Tabard(6) accept any armor type
                                -- For other slots: classID 4 = Armor, check subclassID matches player
                                -- subclassID: 1=Cloth, 2=Leather, 3=Mail, 4=Plate, 0=Misc/Cosmetic
                                local armorOK = true
                                if slotInfo.cat ~= 3 and slotInfo.cat ~= 5 and slotInfo.cat ~= 6 then
                                    if classID == 4 and subclassID and subclassID > 0 then
                                        armorOK = (subclassID == playerArmorSubclass)
                                    end
                                end
                                if armorOK then
                                    table.insert(appearances, { itemID = itemID, visualID = app.visualID })
                                end
                            end
                        end
                    end
                end
            end
        end
        
        if #appearances > 0 then
            local randomIndex = math.random(1, #appearances)
            local item = appearances[randomIndex]
            if item.itemID then
                f.previewModel:TryOn("item:" .. item.itemID)
                addToSlot(catToSlotName[slotInfo.cat], item.itemID)
                itemsApplied = itemsApplied + 1
            end
        end
    end
    
    -- Weapon slots: one random Main Hand, one random Off Hand
    local mainHandCats = { 12, 13, 14, 15, 16, 17, 20, 21, 22, 23, 24, 25, 26, 27 }
    local offHandCats = { 18, 19 }
    local function collectItemIDsFromCategories(catList)
        local out = {}
        if not C_TransmogCollection or not C_TransmogCollection.GetCategoryAppearances then return out end
        for _, categoryID in ipairs(catList) do
            local catAppearances = C_TransmogCollection.GetCategoryAppearances(categoryID)
            if catAppearances then
                for _, app in ipairs(catAppearances) do
                    if app.isCollected then
                        local sources = C_TransmogCollection.GetAppearanceSources(app.visualID)
                        local src = sources[1]
                        local id = src and (src.itemID or src.itemId)
                        if id then
                            table.insert(out, id)
                        end
                    end
                end
            end
        end
        return out
    end
    local mainHandItems = collectItemIDsFromCategories(mainHandCats)
    if #mainHandItems > 0 then
        local itemID = mainHandItems[math.random(1, #mainHandItems)]
        f.previewModel:TryOn("item:" .. itemID)
        addToSlot("MainHand", itemID)
        itemsApplied = itemsApplied + 1
    end
    local offHandItems = collectItemIDsFromCategories(offHandCats)
    if #offHandItems > 0 then
        local itemID = offHandItems[math.random(1, #offHandItems)]
        f.previewModel:TryOn("item:" .. itemID)
        addToSlot("OffHand", itemID)
        itemsApplied = itemsApplied + 1
    end
    
    if itemsApplied > 0 then
        f.previewItemName:SetText("|cff00ff00Random " .. playerArmorType .. " Outfit|r")
        -- Rebuild model from livePreviewSlots so every applied slot is shown (avoids TryOn order issues)
        MF:RebuildLivePreview()
    else
        f.previewItemName:SetText("|cffff0000No collected items found|r")
        MF:UpdateSlotButtons()
    end
end

-- Handle profile changes
ETB.EventBus:Register("ETB_PROFILE_CHANGED", function(key, profile)
    if MF.frame and MF.frame:IsShown() then
        -- Debug: Show what profile we're switching to
        local classID = profile and profile.classID or "nil"
        local primaryArmor = ETB.BlizzardData and ETB.BlizzardData:GetPlayerPrimaryArmor() or "unknown"
        ETB:Debug("MainFrame", "Profile Switch: Class " .. tostring(classID) .. " → Primary Armor: " .. tostring(primaryArmor))
        
        -- Clear caches so items are re-filtered for new class
        if ETB.BlizzardData then
            ETB.BlizzardData:ClearCache()
        end
        -- Refresh items with new class filtering
        MF:RefreshItems()
        -- Update the viewing label using CATEGORY_INFO
        local categoryID = ETB.Filtering.filters.category
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[categoryID]
        local slotName = catInfo and catInfo.name or "Head"
        MF:UpdateCurrentViewingLabel(slotName)
        -- Update profile selector display
        if ETB.ProfileSelector then
            ETB.ProfileSelector:Update()
        end
    end
end)

ETB:Debug("MainFrame", "MainFrame.lua loaded (V8 UI - Comprehensive Fix)")
