--[[
============================================================================
ETB Set Builder - Built-in module for Enhanced Transmog Browser
============================================================================
Create custom transmog sets by clicking items. Export or send to ETB custom sets.

Usage:
  /setbuilder or /sb - Open the Set Builder UI
  /sb clear - Clear current set
  /sb export - Export current set to chat (copyable)
  /sb list - List saved sets
  /sb save <name> - Save current set with a name
  /sb load <name> - Load a saved set
  /sb delete <name> - Delete a saved set
============================================================================
]]--

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

-- Saved Variables (same name as standalone addon for compatibility)
ETB_SetBuilder_Data = ETB_SetBuilder_Data or {
    savedSets = {},
    version = 1,
}

ETB.SetBuilder = ETB.SetBuilder or {}
local SB = ETB.SetBuilder

SB.frame = nil
SB.currentSet = {
    name = "Unnamed Set",
    armorType = nil,
    source = "",
    expansion = 0,
    faction = nil,
    items = {},  -- [slot] = {itemID, name, icon}
}
SB.allItems = {}
SB.filteredItems = {}
SB.currentPage = 1
SB.itemsPerPage = 100
SB.searchText = ""
SB.filterSlot = "all"
SB.filterArmorType = "all"

-- =============================================================================
-- SLOT CONSTANTS (matching CustomSets.lua)
-- =============================================================================

local SLOTS = {
    HEAD = 1,
    SHOULDER = 2,
    CHEST = 3,
    WAIST = 4,
    LEGS = 5,
    FEET = 6,
    WRIST = 7,
    HANDS = 8,
    BACK = 9,
}

local SLOT_NAMES = {
    [1] = "Head",
    [2] = "Shoulders",
    [3] = "Chest",
    [4] = "Waist",
    [5] = "Legs",
    [6] = "Feet",
    [7] = "Wrists",
    [8] = "Hands",
    [9] = "Back",
}

-- Inventory type to slot mapping
local INVTYPE_TO_SLOT = {
    INVTYPE_HEAD = 1,
    INVTYPE_SHOULDER = 2,
    INVTYPE_CHEST = 3,
    INVTYPE_ROBE = 3,
    INVTYPE_WAIST = 4,
    INVTYPE_LEGS = 5,
    INVTYPE_FEET = 6,
    INVTYPE_WRIST = 7,
    INVTYPE_HAND = 8,
    INVTYPE_CLOAK = 9,
}

local EXPANSION_NAMES = {
    [0] = "Classic",
    [1] = "TBC",
    [2] = "Wrath",
    [3] = "Cata",
    [4] = "MoP",
}

-- =============================================================================
-- INITIALIZATION
-- =============================================================================

local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_LOGIN")
initFrame:SetScript("OnEvent", function(self, event)
    if event == "PLAYER_LOGIN" then
        SB:Initialize()
    end
end)

function SB:Initialize()
    if ETB_SetBuilder_Data.savedSets then
        -- Data already loaded
    end
    ETB:Debug("SetBuilder", "Built-in. Type /setbuilder or /sb to open.")
end

-- =============================================================================
-- SLASH COMMANDS
-- =============================================================================

SLASH_SETBUILDER1 = "/setbuilder"
SLASH_SETBUILDER2 = "/sb"

SlashCmdList["SETBUILDER"] = function(msg)
    local args = {}
    for word in msg:gmatch("%S+") do
        args[#args + 1] = word
    end
    
    local cmd = args[1] and args[1]:lower() or ""
    
    if cmd == "" or cmd == "show" then
        SB:Show()
    elseif cmd == "hide" then
        SB:Hide()
    elseif cmd == "clear" then
        SB:ClearCurrentSet()
        ETB:Print("|cff00ff00[Set Builder]|r Current set cleared.")
    elseif cmd == "export" then
        SB:ExportCurrentSet()
    elseif cmd == "list" then
        SB:ListSavedSets()
    elseif cmd == "save" then
        local name = table.concat(args, " ", 2)
        if name and name ~= "" then
            SB:SaveSet(name)
        else
            ETB:Print("|cffff0000[Set Builder]|r Usage: /sb save <set name>")
        end
    elseif cmd == "load" then
        local name = table.concat(args, " ", 2)
        if name and name ~= "" then
            SB:LoadSet(name)
        else
            print("|cffff0000[Set Builder]|r Usage: /sb load <set name>")
        end
    elseif cmd == "delete" then
        local name = table.concat(args, " ", 2)
        if name and name ~= "" then
            SB:DeleteSet(name)
        else
            ETB:Print("|cffff0000[Set Builder]|r Usage: /sb delete <set name>")
        end
    elseif cmd == "help" then
        ETB:Print("|cff00ffff=== ETB Set Builder Commands ===|r")
        ETB:Print("|cffffd100/sb|r - Open Set Builder")
        ETB:Print("|cffffd100/sb clear|r - Clear current set")
        ETB:Print("|cffffd100/sb export|r - Export set to chat")
        ETB:Print("|cffffd100/sb list|r - List saved sets")
        ETB:Print("|cffffd100/sb save <name>|r - Save current set")
        ETB:Print("|cffffd100/sb load <name>|r - Load a saved set")
        ETB:Print("|cffffd100/sb delete <name>|r - Delete a saved set")
    else
        ETB:Print("|cffff0000[Set Builder]|r Unknown command. Type /sb help for help.")
    end
end

-- =============================================================================
-- UI CREATION
-- =============================================================================

function SB:Show()
    -- Always use the standalone window (not the embedded panel)
    if not self.standaloneFrame then
        self:CreateUI()
        self.standaloneFrame = self.frame
    end
    
    -- Restore standalone refs if ShowAsPanel overwrote them
    self.frame = self.standaloneFrame
    
    self.frame:Show()
    self:LoadAllItems()
end

function SB:Hide()
    if self.standaloneFrame then
        self.standaloneFrame:Hide()
    elseif self.frame then
        self.frame:Hide()
    end
    -- Re-show main addon frame if we hid it when opening Builder
    local ETB = NS and NS.ETB
    if ETB and ETB._builderHidMainFrame then
        ETB._builderHidMainFrame = nil
        if _G.ETBMainFrame then
            _G.ETBMainFrame:Show()
        end
    end
end

-- =============================================================================
-- EMBEDDED UI (red spot in main addon sidebar)
-- =============================================================================

SB.embedPanel = nil
SB.embedParent = nil
SB.embedSlotFrames = {}

function SB:ShowEmbedded(parent)
    if not parent then return end
    if self.embedParent ~= parent or not self.embedPanel then
        self:CreateEmbeddedUI(parent)
    end
    self.embedPanel:Show()
    self:UpdateSetDisplay()
    self:UpdateExportText()
end

function SB:HideEmbedded()
    if self.embedPanel then
        self.embedPanel:Hide()
    end
end

function SB:CreateEmbeddedUI(parent)
    if self.embedPanel and self.embedPanel:GetParent() == parent then
        return
    end
    self.embedParent = parent
    self.embedSlotFrames = {}
    
    local panel = CreateFrame("Frame", nil, parent)
    panel:SetAllPoints()
    panel:SetFrameLevel(parent:GetFrameLevel() + 5)
    
    local scroll = CreateFrame("ScrollFrame", nil, panel, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 2, -2)
    scroll:SetPoint("BOTTOMRIGHT", -18, 2)
    
    local child = CreateFrame("Frame", nil, scroll)
    child:SetWidth(parent:GetWidth() - 24)
    child:SetHeight(1)
    scroll:SetScrollChild(child)
    
    local w = parent:GetWidth() - 24
    local y = 0
    
    local title = child:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    title:SetPoint("TOPLEFT", 4, -y)
    title:SetText("|cffffd100Set Builder|r")
    y = y + 18
    
    local nameLabel = child:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    nameLabel:SetPoint("TOPLEFT", 4, -y)
    nameLabel:SetText("Name:")
    y = y + 14
    
    local nameBox = CreateFrame("EditBox", nil, child, "InputBoxTemplate")
    nameBox:SetSize(w - 8, 18)
    nameBox:SetPoint("TOPLEFT", 4, -y)
    nameBox:SetAutoFocus(false)
    nameBox:SetText(SB.currentSet.name or "Unnamed Set")
    nameBox:SetScript("OnTextChanged", function(self)
        SB.currentSet.name = self:GetText()
        SB:UpdateExportText()
    end)
    nameBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    y = y + 22
    
    local sourceLabel = child:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    sourceLabel:SetPoint("TOPLEFT", 4, -y)
    sourceLabel:SetText("Source:")
    y = y + 14
    
    local sourceBox = CreateFrame("EditBox", nil, child, "InputBoxTemplate")
    sourceBox:SetSize(w - 8, 18)
    sourceBox:SetPoint("TOPLEFT", 4, -y)
    sourceBox:SetAutoFocus(false)
    sourceBox:SetText(SB.currentSet.source or "")
    sourceBox:SetScript("OnTextChanged", function(self)
        SB.currentSet.source = self:GetText()
        SB:UpdateExportText()
    end)
    sourceBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    y = y + 24
    
    local rowH = 16
    for slot = 1, 9 do
        local row = CreateFrame("Frame", nil, child)
        row:SetSize(w, rowH)
        row:SetPoint("TOPLEFT", 4, -y)
        
        local slotLabel = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        slotLabel:SetPoint("LEFT", 0, 0)
        slotLabel:SetWidth(48)
        slotLabel:SetJustifyH("LEFT")
        slotLabel:SetText("|cff888888" .. (SLOT_NAMES[slot] or "?") .. "|r")
        
        local icon = row:CreateTexture(nil, "ARTWORK")
        icon:SetSize(14, 14)
        icon:SetPoint("LEFT", 50, 0)
        icon:SetTexture("Interface\\PaperDoll\\UI-Backpack-EmptySlot")
        row.icon = icon
        
        local itemName = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        itemName:SetPoint("LEFT", icon, "RIGHT", 2, 0)
        itemName:SetWidth(w - 72)
        itemName:SetJustifyH("LEFT")
        itemName:SetWordWrap(false)
        itemName:SetText("|cff666666(empty)|r")
        row.itemName = itemName
        
        SB.embedSlotFrames[slot] = row
        y = y + rowH + 1
    end
    
    y = y + 6
    
    local browseBtn = CreateFrame("Button", nil, child, "UIPanelButtonTemplate")
    browseBtn:SetSize(w, 20)
    browseBtn:SetPoint("TOPLEFT", 4, -y)
    browseBtn:SetText("Browse items...")
    browseBtn:SetScript("OnClick", function() SB:Show() end)
    browseBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText("Open full Set Builder to pick items")
        GameTooltip:Show()
    end)
    browseBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    y = y + 26
    
    local btnW = (w - 8) / 2
    local clearBtn = CreateFrame("Button", nil, child, "UIPanelButtonTemplate")
    clearBtn:SetSize(btnW - 2, 22)
    clearBtn:SetPoint("TOPLEFT", 4, -y)
    clearBtn:SetText("Clear")
    clearBtn:SetScript("OnClick", function() SB:ClearCurrentSet() end)
    
    local sendBtn = CreateFrame("Button", nil, child, "UIPanelButtonTemplate")
    sendBtn:SetSize(btnW - 2, 22)
    sendBtn:SetPoint("TOPLEFT", clearBtn, "TOPRIGHT", 4, 0)
    sendBtn:SetText("Send to ETB")
    sendBtn:SetScript("OnClick", function() SB:SendToETB() end)
    y = y + 26
    
    child:SetHeight(y)
    
    self.embedPanel = panel
    self.embedNameBox = nameBox
    self.embedSourceBox = sourceBox
end

function SB:CreateUI()
    -- Main frame - wider to fit mannequin
    local f = CreateFrame("Frame", "ETB_SetBuilderFrame", UIParent, "BackdropTemplate")
    f:SetSize(1300, 780)
    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:SetClampedToScreen(true)
    
    -- ESC key closes the window (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)
    
    -- Title
    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOP", 0, -18)
    title:SetText("|cffff6600ETB Set Builder|r")
    
    -- Close button
    local closeBtn = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    closeBtn:SetPoint("TOPRIGHT", -8, -8)
    
    -- Re-show main addon frame when Builder is closed (by close button, ESC, etc.)
    f:SetScript("OnHide", function()
        local ETBCore = NS and NS.ETB
        if ETBCore and ETBCore._builderHidMainFrame then
            ETBCore._builderHidMainFrame = nil
            if _G.ETBMainFrame then
                _G.ETBMainFrame:Show()
            end
        end
    end)
    
    -- Create panels (Export/code panel hidden - use /sb export to copy set)
    self:CreateMannequinPanel(f)
    self:CreateSetPanel(f)
    self:CreateItemBrowser(f)
    self:CreateExportPanel(f)
    if self.exportPanel then self.exportPanel:Hide() end
    
    self.frame = f
    self.standaloneFrame = f
end

-- =============================================================================
-- MANNEQUIN PANEL (Far left - 3D model preview)
-- =============================================================================

function SB:CreateMannequinPanel(parent)
    local panel = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    panel:SetSize(200, 450)
    panel:SetPoint("TOPLEFT", 15, -50)
    panel: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 }
    })
    
    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOP", 0, -10)
    title:SetText("|cff00ffffSet Preview|r")
    
    -- Create the DressUpModel
    local model = CreateFrame("DressUpModel", nil, panel)
    model:SetSize(180, 380)
    model:SetPoint("TOP", 0, -30)
    model:SetUnit("player")
    model:SetRotation(0)
    model:SetPortraitZoom(0)  -- Full body view
    
    -- Model controls
    local rotateLeft = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    rotateLeft:SetSize(30, 22)
    rotateLeft:SetPoint("BOTTOMLEFT", 15, 12)
    rotateLeft:SetText("<")
    rotateLeft:SetScript("OnClick", function()
        local rotation = model:GetFacing() or 0
        model:SetFacing(rotation + 0.3)
    end)
    
    local rotateRight = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    rotateRight:SetSize(30, 22)
    rotateRight:SetPoint("BOTTOMRIGHT", -15, 12)
    rotateRight:SetText(">")
    rotateRight:SetScript("OnClick", function()
        local rotation = model:GetFacing() or 0
        model:SetFacing(rotation - 0.3)
    end)
    
    local undressBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    undressBtn:SetSize(80, 22)
    undressBtn:SetPoint("BOTTOM", 0, 12)
    undressBtn:SetText("Undress")
    undressBtn:SetScript("OnClick", function()
        model:Undress()
        SB:UpdateMannequin()
    end)
    
    self.mannequin = model
    self.mannequinPanel = panel
end

function SB:UpdateMannequin()
    if not self.mannequin then return end
    
    -- Reset model to player
    self.mannequin:SetUnit("player")
    self.mannequin:Undress()
    
    -- Apply all items in the current set
    for slot, itemData in pairs(self.currentSet.items) do
        if itemData and itemData.itemID then
            self.mannequin:TryOn("item:" .. itemData.itemID)
        end
    end
end

-- =============================================================================
-- SET PANEL (Shows current set being built - next to mannequin)
-- =============================================================================

function SB:CreateSetPanel(parent)
    local panel = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    panel:SetSize(300, 450)
    panel:SetPoint("TOPLEFT", 230, -50)  -- Next to mannequin
    panel: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 }
    })
    
    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOP", 0, -10)
    title:SetText("|cff00ffffCurrent Set|r")
    
    -- Set Name input
    local nameLabel = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    nameLabel:SetPoint("TOPLEFT", 10, -35)
    nameLabel:SetText("Set Name:")
    
    local nameBox = CreateFrame("EditBox", nil, panel, "InputBoxTemplate")
    nameBox:SetSize(270, 22)
    nameBox:SetPoint("TOPLEFT", 10, -50)
    nameBox:SetAutoFocus(false)
    nameBox:SetText("Unnamed Set")
    nameBox:SetScript("OnTextChanged", function(self)
        SB.currentSet.name = self:GetText()
        SB:UpdateExportText()
    end)
    nameBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    self.nameBox = nameBox
    
    -- Source input
    local sourceLabel = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    sourceLabel:SetPoint("TOPLEFT", 10, -78)
    sourceLabel:SetText("Source (Dungeon/Zone):")
    
    local sourceBox = CreateFrame("EditBox", nil, panel, "InputBoxTemplate")
    sourceBox:SetSize(270, 22)
    sourceBox:SetPoint("TOPLEFT", 10, -93)
    sourceBox:SetAutoFocus(false)
    sourceBox:SetText("")
    sourceBox:SetScript("OnTextChanged", function(self)
        SB.currentSet.source = self:GetText()
        SB:UpdateExportText()
    end)
    sourceBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    self.sourceBox = sourceBox
    
    -- Armor Type dropdown
    local armorLabel = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    armorLabel:SetPoint("TOPLEFT", 10, -120)
    armorLabel:SetText("Armor Type:")
    
    local armorDropdown = ETBDropdown:Create("SBArmorTypeDropdown", panel)
    armorDropdown:SetPoint("TOPLEFT", -5, -135)
    ETBDropdown:SetWidth(armorDropdown, 100)
    ETBDropdown:SetText(armorDropdown, "Auto-detect")
    ETBDropdown:Initialize(armorDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        local types = {"Auto-detect", "CLOTH", "LEATHER", "MAIL", "PLATE"}
        for _, t in ipairs(types) do
            info.text = t
            info.value = t
            info.func = function()
                ETBDropdown:SetText(armorDropdown, t)
                SB.currentSet.armorType = (t == "Auto-detect") and nil or t
                SB:UpdateExportText()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    
    -- Expansion dropdown
    local expLabel = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    expLabel:SetPoint("TOPLEFT", 150, -120)
    expLabel:SetText("Expansion:")
    
    local expDropdown = ETBDropdown:Create("SBExpansionDropdown", panel)
    expDropdown:SetPoint("TOPLEFT", 135, -135)
    ETBDropdown:SetWidth(expDropdown, 80)
    ETBDropdown:SetText(expDropdown, "Classic")
    ETBDropdown:Initialize(expDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        for i = 0, 4 do
            info.text = EXPANSION_NAMES[i]
            info.value = i
            info.func = function()
                ETBDropdown:SetText(expDropdown, EXPANSION_NAMES[i])
                SB.currentSet.expansion = i
                SB:UpdateExportText()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    
    -- Slot display (shows items in each slot) - compact layout
    local slotContainer = CreateFrame("Frame", nil, panel)
    slotContainer:SetPoint("TOPLEFT", 10, -170)
    slotContainer:SetPoint("BOTTOMRIGHT", -10, 45)
    
    local slotY = 0
    self.slotFrames = {}
    
    for slot = 1, 9 do
        local slotFrame = CreateFrame("Button", nil, slotContainer)
        slotFrame:SetSize(270, 26)  -- Smaller height
        slotFrame:SetPoint("TOPLEFT", 0, -slotY)
        slotFrame:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD")
        
        local bg = slotFrame:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints()
        bg:SetColorTexture(0.1, 0.1, 0.1, 0.5)
        
        local slotLabel = slotFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        slotLabel:SetPoint("LEFT", 5, 0)
        slotLabel:SetWidth(60)
        slotLabel:SetJustifyH("LEFT")
        slotLabel:SetText("|cffffd100" .. (SLOT_NAMES[slot] or "?") .. ":|r")
        
        local icon = slotFrame:CreateTexture(nil, "ARTWORK")
        icon:SetSize(22, 22)  -- Smaller icon
        icon:SetPoint("LEFT", 65, 0)
        icon:SetTexture("Interface\\PaperDoll\\UI-Backpack-EmptySlot")
        slotFrame.icon = icon
        
        local itemName = slotFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        itemName:SetPoint("LEFT", icon, "RIGHT", 5, 0)
        itemName:SetWidth(140)
        itemName:SetJustifyH("LEFT")
        itemName:SetWordWrap(false)
        itemName:SetText("|cff888888(empty)|r")
        slotFrame.itemName = itemName
        
        -- Remove button
        local removeBtn = CreateFrame("Button", nil, slotFrame)
        removeBtn:SetSize(16, 16)
        removeBtn:SetPoint("RIGHT", -3, 0)
        removeBtn:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
        removeBtn:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Highlight")
        removeBtn:SetScript("OnClick", function()
            SB:RemoveFromSlot(slot)
        end)
        removeBtn:Hide()
        slotFrame.removeBtn = removeBtn
        
        slotFrame.slot = slot
        slotFrame:SetScript("OnClick", function()
            SB.filterSlot = slot
            ETBDropdown:SetText(SB.slotDropdown, SLOT_NAMES[slot])
            SB:FilterItems()
        end)
        
        self.slotFrames[slot] = slotFrame
        slotY = slotY + 28  -- Compact spacing
    end
    
    -- Clear, Send to ETB, and Export buttons at the very bottom
    local clearBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    clearBtn:SetSize(100, 26)
    clearBtn:SetPoint("BOTTOMLEFT", 10, 12)
    clearBtn:SetText("Clear Set")
    clearBtn:SetScript("OnClick", function()
        SB:ClearCurrentSet()
    end)
    
    local sendToETBBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    sendToETBBtn:SetSize(120, 26)
    sendToETBBtn:SetPoint("BOTTOM", 0, 12)
    sendToETBBtn:SetText("Send to ETB")
    sendToETBBtn:SetScript("OnClick", function()
        SB:SendToETB()
    end)
    sendToETBBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:AddLine("Send to Enhanced Transmog Browser")
        GameTooltip:AddLine("Imports this set as a custom set in ETB", 1, 1, 1)
        GameTooltip:Show()
    end)
    sendToETBBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    
    local exportBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    exportBtn:SetSize(100, 26)
    exportBtn:SetPoint("BOTTOMRIGHT", -10, 12)
    exportBtn:SetText("Export")
    exportBtn:SetScript("OnClick", function()
        SB:ExportCurrentSet()
    end)
    
    self.setPanel = panel
end

-- =============================================================================
-- ITEM BROWSER (Center - shows all transmog items)
-- =============================================================================

function SB:CreateItemBrowser(parent)
    local panel = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    panel:SetSize(480, 700)
    panel:SetPoint("TOPLEFT", 545, -50)  -- Next to set panel
    panel: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 }
    })
    
    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOP", 0, -10)
    title:SetText("|cff00ffffItem Browser|r")
    
    -- Search box
    local searchBox = CreateFrame("EditBox", nil, panel, "InputBoxTemplate")
    searchBox:SetSize(220, 22)
    searchBox:SetPoint("TOPLEFT", 10, -35)
    searchBox:SetAutoFocus(false)
    searchBox:SetScript("OnTextChanged", function(self)
        SB.searchText = self:GetText()  -- Don't lowercase here, FilterItems handles it
        SB.currentPage = 1
        SB:FilterItems()
    end)
    searchBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    self.searchBox = searchBox
    
    -- Slot filter dropdown
    local slotDropdown = ETBDropdown:Create("SBSlotFilterDropdown", panel)
    slotDropdown:SetPoint("TOPLEFT", 220, -30)
    ETBDropdown:SetWidth(slotDropdown, 90)
    ETBDropdown:SetText(slotDropdown, "All Slots")
    ETBDropdown:Initialize(slotDropdown, function(self, _items_)
        local info = ETBDropdown:CreateInfo()
        info.text = "All Slots"
        info.value = "all"
        info.func = function()
            ETBDropdown:SetText(slotDropdown, "All Slots")
            SB.filterSlot = "all"
            SB.currentPage = 1
            SB:FilterItems()
        end
        ETBDropdown:AddButton(_items_, info)
        
        for slot = 1, 9 do
            info.text = SLOT_NAMES[slot]
            info.value = slot
            info.func = function()
                ETBDropdown:SetText(slotDropdown, SLOT_NAMES[slot])
                SB.filterSlot = slot
                SB.currentPage = 1
                SB:FilterItems()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    self.slotDropdown = slotDropdown
    
    -- Armor type filter
    local armorFilterDropdown = ETBDropdown:Create("SBArmorFilterDropdown", panel)
    armorFilterDropdown:SetPoint("TOPLEFT", 340, -30)
    ETBDropdown:SetWidth(armorFilterDropdown, 70)
    ETBDropdown:SetText(armorFilterDropdown, "All")
    ETBDropdown:Initialize(armorFilterDropdown, function(self, _items_)
        local types = {"All", "CLOTH", "LEATHER", "MAIL", "PLATE"}
        for _, t in ipairs(types) do
            local info = ETBDropdown:CreateInfo()
            local filterValue = t  -- Capture value for closure
            info.text = t
            info.value = t
            info.func = function()
                ETBDropdown:SetText(armorFilterDropdown, filterValue)
                SB.filterArmorType = (filterValue == "All") and "all" or filterValue
                SB.currentPage = 1
                SB:FilterItems()
            end
            info.checked = (SB.filterArmorType == ((t == "All") and "all" or t))
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    
    -- Stats
    local stats = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    stats:SetPoint("TOPLEFT", 10, -60)
    stats:SetText("|cff888888Loading items...|r")
    self.statsText = stats
    
    -- Scroll frame
    local scrollContainer = CreateFrame("Frame", nil, panel)
    scrollContainer:SetPoint("TOPLEFT", 5, -80)
    scrollContainer:SetPoint("BOTTOMRIGHT", -25, 40)
    
    local scrollFrame = CreateFrame("ScrollFrame", nil, scrollContainer, "UIPanelScrollFrameTemplate")
    scrollFrame:SetAllPoints()
    
    local scrollChild = CreateFrame("Frame", nil, scrollFrame)
    scrollChild:SetSize(400, 1)
    scrollFrame:SetScrollChild(scrollChild)
    
    self.scrollFrame = scrollFrame
    self.scrollChild = scrollChild
    self.itemButtons = {}
    
    -- Navigation buttons
    local prevBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    prevBtn:SetSize(80, 22)
    prevBtn:SetPoint("BOTTOMLEFT", 10, 10)
    prevBtn:SetText("« Prev")
    prevBtn:SetScript("OnClick", function()
        if SB.currentPage > 1 then
            SB.currentPage = SB.currentPage - 1
            SB:DisplayItems()
        end
    end)
    self.prevBtn = prevBtn
    
    local pageText = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    pageText:SetPoint("BOTTOM", 0, 15)
    self.pageText = pageText
    
    local nextBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    nextBtn:SetSize(80, 22)
    nextBtn:SetPoint("BOTTOMRIGHT", -10, 10)
    nextBtn:SetText("Next »")
    nextBtn:SetScript("OnClick", function()
        local totalPages = math.ceil(#SB.filteredItems / SB.itemsPerPage)
        if SB.currentPage < totalPages then
            SB.currentPage = SB.currentPage + 1
            SB:DisplayItems()
        end
    end)
    self.nextBtn = nextBtn
    
    self.browserPanel = panel
end

-- =============================================================================
-- EXPORT PANEL (Right side - shows export text)
-- =============================================================================

function SB:CreateExportPanel(parent)
    local panel = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    panel:SetSize(270, 670)
    panel:SetPoint("TOPRIGHT", -15, -50)
    panel: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 }
    })
    
    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOP", 0, -10)
    title:SetText("|cff00ffffExport / Saved Sets|r")
    
    -- Copy hint
    local hint = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    hint:SetPoint("TOP", 0, -28)
    hint:SetText("|cff888888Ctrl+C to copy|r")
    
    -- Export text (copyable)
    local exportScroll = CreateFrame("ScrollFrame", nil, panel, "UIPanelScrollFrameTemplate")
    exportScroll:SetPoint("TOPLEFT", 10, -45)
    exportScroll:SetPoint("BOTTOMRIGHT", -30, 150)
    
    local exportBox = CreateFrame("EditBox", nil, exportScroll)
    exportBox:SetMultiLine(true)
    exportBox:SetAutoFocus(false)
    exportBox:SetFontObject("GameFontHighlightSmall")
    exportBox:SetWidth(220)
    exportBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    exportScroll:SetScrollChild(exportBox)
    self.exportBox = exportBox
    
    -- Saved sets list
    local savedLabel = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    savedLabel:SetPoint("BOTTOMLEFT", 10, 100)
    savedLabel:SetText("|cffffd100Saved Sets:|r")
    
    -- Saved sets scroll
    local savedScroll = CreateFrame("ScrollFrame", nil, panel, "UIPanelScrollFrameTemplate")
    savedScroll:SetPoint("TOPLEFT", savedLabel, "BOTTOMLEFT", 0, -5)
    savedScroll:SetSize(210, 60)
    
    local savedChild = CreateFrame("Frame", nil, savedScroll)
    savedChild:SetSize(190, 1)
    savedScroll:SetScrollChild(savedChild)
    self.savedScrollChild = savedChild
    self.savedButtons = {}
    
    -- Save button
    local saveBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    saveBtn:SetSize(100, 24)
    saveBtn:SetPoint("BOTTOMLEFT", 10, 10)
    saveBtn:SetText("Save Set")
    saveBtn:SetScript("OnClick", function()
        local name = SB.currentSet.name
        if name and name ~= "" and name ~= "Unnamed Set" then
            SB:SaveSet(name)
        else
            ETB:Print("|cffff0000[Set Builder]|r Please name your set first!")
        end
    end)
    
    self.exportPanel = panel
end

-- =============================================================================
-- LOAD ALL ARMOR ITEMS (Using C_TransmogCollection - same as main addon)
-- =============================================================================

-- Armor subclass to type mapping
local ARMOR_SUBCLASS_MAP = {
    [1] = "CLOTH",
    [2] = "LEATHER", 
    [3] = "MAIL",
    [4] = "PLATE",
}

-- Category ID to Set Builder slot mapping
local CATEGORY_TO_SLOT = {
    [1] = 1,   -- Head
    [2] = 2,   -- Shoulder
    [3] = 9,   -- Back (Cloak)
    [4] = 3,   -- Chest
    [5] = nil, -- Shirt (skip)
    [6] = nil, -- Tabard (skip)
    [7] = 7,   -- Wrist
    [8] = 8,   -- Hands
    [9] = 4,   -- Waist
    [10] = 5,  -- Legs
    [11] = 6,  -- Feet
}

function SB:LoadAllItems()
    self.allItems = {}
    self.pendingNames = {}  -- Track items needing name resolution
    self.loadingComplete = false
    
    -- Comprehensive tracking to find where items are lost
    self.loadStats = {
        byCategory = {},
        totalAppearances = 0,
        totalSources = 0,
        totalItems = 0,
        skippedNoItemID = 0,
        skippedNoSlot = 0,
        errors = 0,
    }
    
    self.statsText:SetText("|cffffd100Loading transmog items...|r")
    
    -- Valid transmog categories in MoP Classic
    -- Categories 1-11 are armor slots, 12-13 are the main weapon categories
    local validCategories = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
    
    local categoryNames = {
        [1] = "Head", [2] = "Shoulder", [3] = "Back", [4] = "Chest", [5] = "Shirt",
        [6] = "Tabard", [7] = "Wrist", [8] = "Hands", [9] = "Waist", [10] = "Legs", [11] = "Feet",
        [12] = "Main Hand", [13] = "Off Hand"
    }
    
    local currentIndex = 1
    
    local function LoadCategory()
        if currentIndex > #validCategories then
            -- All categories loaded, now resolve pending names
            SB:ResolveItemNames()
            return
        end
        
        local categoryID = validCategories[currentIndex]
        local catName = categoryNames[categoryID] or ("Category " .. categoryID)
        
        SB.statsText:SetText(string.format("|cffffd100Loading %s... (%d items so far)|r", catName, #SB.allItems))
        
        -- Track stats for this category
        local catStats = {
            name = catName,
            appearances = 0,
            sources = 0,
            items = 0,
            noItemID = 0,
            errors = 0,
        }
        
        -- Get appearances for this category
        local success, appearances = pcall(C_TransmogCollection.GetCategoryAppearances, categoryID)
        
        if not success then
            catStats.errors = catStats.errors + 1
            ETB:Print(string.format("|cffff0000[Set Builder]|r ERROR getting appearances for %s: %s", catName, tostring(appearances)))
        elseif appearances and #appearances > 0 then
            catStats.appearances = #appearances
            SB.loadStats.totalAppearances = SB.loadStats.totalAppearances + #appearances
            
            for _, appearance in ipairs(appearances) do
                -- Get ALL item sources for this appearance
                local srcSuccess, sources = pcall(C_TransmogCollection.GetAppearanceSources, appearance.visualID)
                
                if not srcSuccess then
                    catStats.errors = catStats.errors + 1
                elseif sources then
                    catStats.sources = catStats.sources + #sources
                    SB.loadStats.totalSources = SB.loadStats.totalSources + #sources
                    
                    for _, source in ipairs(sources) do
                        if source.itemID then
                            -- Get item info from GetItemInfoInstant
                            local _, _, _, equipLoc, itemIcon, itemClassID, itemSubClassID = GetItemInfoInstant(source.itemID)
                            
                            -- Determine type (armor or weapon)
                            local armorType = nil
                            local weaponType = nil
                            if itemClassID == 4 then -- Armor
                                armorType = ARMOR_SUBCLASS_MAP[itemSubClassID]
                            elseif itemClassID == 2 then -- Weapon
                                weaponType = "WEAPON"
                            end
                            
                            -- Map slot - ALWAYS assign a slot, never skip
                            local slot = INVTYPE_TO_SLOT[equipLoc]
                            if not slot then
                                slot = CATEGORY_TO_SLOT[categoryID]
                            end
                            if not slot then
                                -- Fallback: use category as slot for armor, 10 for weapons
                                if categoryID <= 11 then
                                    slot = categoryID
                                else
                                    slot = 10
                                end
                            end
                            
                            -- Try to get name from GetItemInfo (may be nil if not cached)
                            local itemName, _, quality = GetItemInfo(source.itemID)
                            
                            local item = {
                                itemID = source.itemID,
                                name = itemName,  -- May be nil
                                icon = itemIcon or source.itemTexture or "Interface\\Icons\\INV_Misc_QuestionMark",
                                slot = slot,
                                armorType = armorType,
                                weaponType = weaponType,
                                quality = quality or 2,
                                isCollected = source.isCollected,
                                visualID = appearance.visualID,
                                categoryID = categoryID,
                                sourceID = source.sourceID,  -- Track sourceID too
                            }
                            
                            table.insert(SB.allItems, item)
                            catStats.items = catStats.items + 1
                            SB.loadStats.totalItems = SB.loadStats.totalItems + 1
                            
                            -- Track items without names for later resolution
                            if not itemName then
                                SB.pendingNames[source.itemID] = item
                            end
                        else
                            catStats.noItemID = catStats.noItemID + 1
                            SB.loadStats.skippedNoItemID = SB.loadStats.skippedNoItemID + 1
                        end
                    end
                end
            end
        end
        
        SB.loadStats.byCategory[categoryID] = catStats
        SB.loadStats.errors = SB.loadStats.errors + catStats.errors
        
        currentIndex = currentIndex + 1
        
        -- Process next category after a brief delay to prevent freezing
        C_Timer.After(0.02, LoadCategory)
    end
    
    -- Start loading
    LoadCategory()
end

function SB:ResolveItemNames()
    local pendingCount = 0
    for _ in pairs(self.pendingNames) do
        pendingCount = pendingCount + 1
    end
    
    if pendingCount == 0 then
        self:VerifyAndFinish()
        return
    end
    
    ETB:Print(string.format("|cff00ffff[Set Builder]|r Resolving %d item names (this may take a moment)...", pendingCount))
    self.statsText:SetText(string.format("|cffffd100Requesting %d item names...|r", pendingCount))
    
    -- PHASE 1: Request all items from server (GetItemInfo triggers server request)
    local requestCount = 0
    for itemID, item in pairs(self.pendingNames) do
        GetItemInfo(itemID)  -- This triggers the server to send item data
        requestCount = requestCount + 1
    end
    
    ETB:Print(string.format("|cff00ffff[Set Builder]|r Requested %d items from server", requestCount))
    
    -- PHASE 2: Register for item info events and resolve in batches
    local resolved = 0
    local totalPending = pendingCount
    local retryCount = 0
    local maxRetries = 20  -- More retries since we're waiting for server
    
    -- Create event frame for GET_ITEM_INFO_RECEIVED
    if not SB.itemInfoFrame then
        SB.itemInfoFrame = CreateFrame("Frame")
    end
    
    local function ProcessPending()
        local stillPending = {}
        local newlyResolved = 0
        
        for itemID, item in pairs(SB.pendingNames) do
            local itemName, _, quality = GetItemInfo(itemID)
            if itemName then
                item.name = itemName
                if type(quality) == "number" then item.quality = quality end
                newlyResolved = newlyResolved + 1
            else
                stillPending[itemID] = item
            end
        end
        
        resolved = resolved + newlyResolved
        SB.pendingNames = stillPending
        
        -- Count remaining
        local remaining = 0
        for _ in pairs(SB.pendingNames) do
            remaining = remaining + 1
        end
        
        local percent = math.floor((resolved / totalPending) * 100)
        SB.statsText:SetText(string.format("|cffffd100Resolving: %d/%d (%d%%) - retry %d|r", 
            resolved, totalPending, percent, retryCount))
        
        if remaining > 0 and retryCount < maxRetries then
            retryCount = retryCount + 1
            -- Re-request unresolved items
            local reRequested = 0
            for itemID, item in pairs(SB.pendingNames) do
                GetItemInfo(itemID)
                reRequested = reRequested + 1
                if reRequested > 1000 then break end  -- Batch requests
            end
            -- Longer delay to let server respond
            C_Timer.After(1.0, ProcessPending)
        else
            -- Done - accept remaining as unnamed
            local unnamed = 0
            for itemID, item in pairs(SB.pendingNames) do
                if not item.name then
                    item.name = "Item #" .. itemID
                    unnamed = unnamed + 1
                end
            end
            
            ETB:Print(string.format("|cff00ff00[Set Builder]|r Named %d/%d items (%.1f%%)", 
                resolved, totalPending, (resolved/totalPending)*100))
            if unnamed > 0 then
                ETB:Print(string.format("|cffffff00[Set Builder]|r %d items remain unnamed (will show as Item #ID)", unnamed))
            end
            
            SB.pendingNames = nil
            SB:VerifyAndFinish()
        end
    end
    
    -- Start processing after initial delay to let first batch of server responses arrive
    C_Timer.After(2.0, ProcessPending)
end

function SB:VerifyAndFinish()
    -- VERIFICATION PHASE - Compare loaded items vs API totals
    self.statsText:SetText("|cffffd100Verifying items...|r")
    
    -- Count totals by category and armor type
    local byCategory = {}
    local byArmorType = { CLOTH = 0, LEATHER = 0, MAIL = 0, PLATE = 0, OTHER = 0 }
    local bySlot = {}
    local namedCount = 0
    local unnamedCount = 0
    local totalItems = #self.allItems
    local weaponCount = 0
    
    for _, item in ipairs(self.allItems) do
        -- Count by category
        byCategory[item.categoryID] = (byCategory[item.categoryID] or 0) + 1
        
        -- Count by slot
        bySlot[item.slot] = (bySlot[item.slot] or 0) + 1
        
        -- Count by armor type
        if item.weaponType then
            weaponCount = weaponCount + 1
        elseif item.armorType then
            byArmorType[item.armorType] = byArmorType[item.armorType] + 1
        else
            byArmorType.OTHER = byArmorType.OTHER + 1
        end
        
        -- Count named vs unnamed
        if item.name and not item.name:match("^Item #") then
            namedCount = namedCount + 1
        else
            unnamedCount = unnamedCount + 1
        end
    end
    
    -- Print comprehensive verification report
    ETB:Print("|cff00ff00============================================|r")
    ETB:Print("|cff00ff00=== SET BUILDER LOAD VERIFICATION ===|r")
    ETB:Print("|cff00ff00============================================|r")
    
    -- API Stats (what we tried to load)
    ETB:Print("")
    ETB:Print("|cffffd100[API TOTALS]|r")
    ETB:Print(string.format("  Appearances tried: %d", self.loadStats.totalAppearances))
    ETB:Print(string.format("  Sources found: %d", self.loadStats.totalSources))
    ETB:Print(string.format("  Items created: %d", self.loadStats.totalItems))
    if self.loadStats.skippedNoItemID > 0 then
        ETB:Print(string.format("  |cffff0000Skipped (no itemID): %d|r", self.loadStats.skippedNoItemID))
    end
    if self.loadStats.errors > 0 then
        ETB:Print(string.format("  |cffff0000Errors: %d|r", self.loadStats.errors))
    end
    
    -- Per-category breakdown
    ETB:Print("")
    ETB:Print("|cffffd100[BY CATEGORY]|r")
    local categoryNames = {
        [1] = "Head", [2] = "Shoulder", [3] = "Back", [4] = "Chest", [5] = "Shirt",
        [6] = "Tabard", [7] = "Wrist", [8] = "Hands", [9] = "Waist", [10] = "Legs", [11] = "Feet",
        [12] = "Main Hand", [13] = "Off Hand"
    }
    for catID = 1, 13 do
        local catStats = self.loadStats.byCategory[catID]
        if catStats then
            local catName = categoryNames[catID] or ("Cat " .. catID)
            local loadedCount = byCategory[catID] or 0
            local status = ""
            if catStats.sources ~= loadedCount then
                status = string.format(" |cffff0000MISSING %d!|r", catStats.sources - loadedCount)
            elseif catStats.noItemID > 0 then
                status = string.format(" |cffffff00(%d no itemID)|r", catStats.noItemID)
            end
            ETB:Print(string.format("  %s: %d appearances → %d sources → %d items%s", 
                catName, catStats.appearances, catStats.sources, loadedCount, status))
        end
    end
    
    -- Armor type breakdown
    ETB:Print("")
    ETB:Print("|cffffd100[BY ARMOR TYPE]|r")
    local armorTotal = byArmorType.CLOTH + byArmorType.LEATHER + byArmorType.MAIL + byArmorType.PLATE
    ETB:Print(string.format("  Cloth: %d | Leather: %d | Mail: %d | Plate: %d | Other: %d | Weapons: %d",
        byArmorType.CLOTH, byArmorType.LEATHER, byArmorType.MAIL, byArmorType.PLATE, 
        byArmorType.OTHER, weaponCount))
    ETB:Print(string.format("  Total Armor: %d | Total Weapons: %d | Grand Total: %d",
        armorTotal + byArmorType.OTHER, weaponCount, totalItems))
    
    -- Names status
    ETB:Print("")
    ETB:Print("|cffffd100[NAMES STATUS]|r")
    ETB:Print(string.format("  Named: %d (%.1f%%) | Unnamed: %d (%.1f%%)", 
        namedCount, (namedCount/math.max(1,totalItems))*100,
        unnamedCount, (unnamedCount/math.max(1,totalItems))*100))
    
    ETB:Print("|cff00ff00============================================|r")
    
    -- Now finish loading
    self:FinishLoading()
end

function SB:FinishLoading()
    -- Sort by name (items without names go to end)
    table.sort(self.allItems, function(a, b)
        local nameA = a.name or "~" .. a.itemID  -- ~ sorts after letters
        local nameB = b.name or "~" .. b.itemID
        return nameA < nameB
    end)
    
    self.loadingComplete = true
    self:FilterItems()
    
    -- Update stats text with final count
    self.statsText:SetText(string.format("|cff00ff00Loaded %d items|r", #self.allItems))
end

-- =============================================================================
-- FILTER AND DISPLAY ITEMS
-- =============================================================================

function SB:FilterItems()
    self.filteredItems = {}
    
    -- Pre-process search text into words for multi-word matching
    local searchWords = {}
    local searchText = self.searchText or ""
    if searchText ~= "" then
        local searchLower = searchText:lower()
        for word in searchLower:gmatch("%S+") do
            table.insert(searchWords, word)
        end
    end
    
    for _, item in ipairs(self.allItems) do
        local include = true
        
        -- Search filter - supports multi-word search
        -- ALL search words must be present in the item name
        if #searchWords > 0 then
            local itemName = (item.name or ""):lower()
            for _, word in ipairs(searchWords) do
                if not itemName:find(word, 1, true) then
                    include = false
                    break
                end
            end
        end
        
        -- Slot filter
        if include and self.filterSlot ~= "all" then
            if item.slot ~= self.filterSlot then
                include = false
            end
        end
        
        -- Armor type filter
        if include and self.filterArmorType ~= "all" then
            if item.armorType ~= self.filterArmorType then
                include = false
            end
        end
        
        if include then
            table.insert(self.filteredItems, item)
        end
    end
    
    self:DisplayItems()
end

function SB:DisplayItems()
    -- Clear existing buttons
    for _, btn in ipairs(self.itemButtons) do
        btn:Hide()
    end
    
    local startIdx = (self.currentPage - 1) * self.itemsPerPage + 1
    local endIdx = math.min(startIdx + self.itemsPerPage - 1, #self.filteredItems)
    
    local yOffset = 0
    local btnHeight = 26
    
    -- Track items needing name resolution on this page
    local needsNameUpdate = {}
    
    for i = startIdx, endIdx do
        local item = self.filteredItems[i]
        local btnIdx = i - startIdx + 1
        
        local btn = self.itemButtons[btnIdx]
        if not btn then
            btn = CreateFrame("Button", nil, self.scrollChild)
            btn:SetSize(400, btnHeight)
            btn:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD")
            
            local bg = btn:CreateTexture(nil, "BACKGROUND")
            bg:SetAllPoints()
            bg:SetColorTexture(0.1, 0.1, 0.1, 0.3)
            btn.bg = bg
            
            local icon = btn:CreateTexture(nil, "ARTWORK")
            icon:SetSize(22, 22)
            icon:SetPoint("LEFT", 2, 0)
            btn.icon = icon
            
            local name = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            name:SetPoint("LEFT", icon, "RIGHT", 5, 0)
            name:SetWidth(250)
            name:SetJustifyH("LEFT")
            btn.itemName = name
            
            local slot = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            slot:SetPoint("LEFT", name, "RIGHT", 5, 0)
            slot:SetWidth(70)
            btn.slotText = slot
            
            local addBtn = CreateFrame("Button", nil, btn, "UIPanelButtonTemplate")
            addBtn:SetSize(40, 20)
            addBtn:SetPoint("RIGHT", -5, 0)
            addBtn:SetText("Add")
            btn.addBtn = addBtn
            
            self.itemButtons[btnIdx] = btn
        end
        
        btn:SetPoint("TOPLEFT", 0, -yOffset)
        btn.icon:SetTexture(item.icon)
        
        -- Try to get name if we don't have it
        local displayName = item.name
        if not displayName or displayName:match("^Item #") then
            -- Try to get name now
            local itemName = GetItemInfo(item.itemID)
            if itemName then
                item.name = itemName
                displayName = itemName
            else
                displayName = "|cff888888Loading...|r"
                needsNameUpdate[item.itemID] = { item = item, btn = btn }
            end
        end
        
        -- Color by quality
        local r, g, b = GetItemQualityColor(item.quality or 1)
        btn.itemName:SetText(displayName)
        if displayName and not displayName:match("Loading") then
            btn.itemName:SetTextColor(r, g, b)
        end
        
        btn.slotText:SetText("|cff888888" .. (SLOT_NAMES[item.slot] or "?") .. "|r")
        
        -- Highlight if collected
        if item.isCollected then
            btn.bg:SetColorTexture(0.1, 0.3, 0.1, 0.3)
        else
            btn.bg:SetColorTexture(0.1, 0.1, 0.1, 0.3)
        end
        
        btn.addBtn:SetScript("OnClick", function()
            SB:AddToSet(item)
        end)
        
        btn:SetScript("OnEnter", function()
            GameTooltip:SetOwner(btn, "ANCHOR_RIGHT")
            if item.itemID and item.itemID > 0 then ETB:SafeSetTooltipItem(GameTooltip, item.itemID) else GameTooltip:SetText(item.name or "Unknown") end
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
        
        btn:Show()
        yOffset = yOffset + btnHeight
    end
    
    self.scrollChild:SetHeight(math.max(yOffset, 1))
    
    -- Update stats and pagination
    local totalPages = math.ceil(#self.filteredItems / self.itemsPerPage)
    self.statsText:SetText(string.format("|cff888888Showing %d-%d of %d items|r", 
        startIdx, endIdx, #self.filteredItems))
    self.pageText:SetText(string.format("Page %d / %d", self.currentPage, math.max(totalPages, 1)))
    
    -- Schedule async name resolution for items on this page
    if next(needsNameUpdate) then
        SB:ScheduleNameUpdate(needsNameUpdate)
    end
end

function SB:ScheduleNameUpdate(items)
    local retries = 0
    local maxRetries = 10
    
    local function TryUpdate()
        local stillPending = false
        for itemID, data in pairs(items) do
            local itemName, _, quality = GetItemInfo(itemID)
            if itemName then
                data.item.name = itemName
                if type(quality) == "number" then data.item.quality = quality end
                local r, g, b = GetItemQualityColor(data.item.quality or 1)
                if data.btn and data.btn.itemName then
                    data.btn.itemName:SetText(itemName)
                    data.btn.itemName:SetTextColor(r, g, b)
                end
                items[itemID] = nil
            else
                stillPending = true
            end
        end
        
        retries = retries + 1
        if stillPending and retries < maxRetries then
            C_Timer.After(0.5, TryUpdate)
        end
    end
    
    C_Timer.After(0.3, TryUpdate)
end

-- =============================================================================
-- SET MANAGEMENT
-- =============================================================================

function SB:AddToSet(item)
    if not item or not item.slot then return end
    
    local slot = item.slot
    self.currentSet.items[slot] = {
        itemID = item.itemID,
        name = item.name,
        icon = item.icon,
    }
    
    -- Auto-detect armor type from first item
    if not self.currentSet.armorType and item.armorType then
        self.currentSet.armorType = item.armorType
        ETBDropdown:SetText(_G["SBArmorTypeDropdown"], item.armorType)
    end
    
    self:UpdateSetDisplay()
    self:UpdateExportText()
    self:UpdateMannequin()  -- Update 3D preview
    
    ETB:Print(string.format("|cff00ff00[Set Builder]|r Added %s to %s slot.", 
        item.name, SLOT_NAMES[slot]))
end

function SB:RemoveFromSlot(slot)
    self.currentSet.items[slot] = nil
    self:UpdateSetDisplay()
    self:UpdateExportText()
    self:UpdateMannequin()  -- Update 3D preview
end

function SB:ClearCurrentSet()
    self.currentSet = {
        name = "Unnamed Set",
        armorType = nil,
        source = "",
        expansion = 0,
        faction = nil,
        items = {},
    }
    
    if self.nameBox then self.nameBox:SetText("Unnamed Set") end
    if self.sourceBox then self.sourceBox:SetText("") end
    if self.embedNameBox then self.embedNameBox:SetText("Unnamed Set") end
    if self.embedSourceBox then self.embedSourceBox:SetText("") end
    if _G["SBArmorTypeDropdown"] then ETBDropdown:SetText(_G["SBArmorTypeDropdown"], "Auto-detect") end
    if _G["SBExpansionDropdown"] then ETBDropdown:SetText(_G["SBExpansionDropdown"], "Classic") end
    
    self:UpdateSetDisplay()
    self:UpdateExportText()
    if self.mannequin then self:UpdateMannequin() end
end

function SB:UpdateSetDisplay()
    for slot = 1, 9 do
        if self.slotFrames then
            local frame = self.slotFrames[slot]
            if frame then
                local itemData = self.currentSet.items[slot]
                if itemData then
                    frame.icon:SetTexture(itemData.icon)
                    frame.itemName:SetText(itemData.name)
                    if frame.removeBtn then frame.removeBtn:Show() end
                else
                    frame.icon:SetTexture("Interface\\PaperDoll\\UI-Backpack-EmptySlot")
                    frame.itemName:SetText("|cff888888(empty)|r")
                    if frame.removeBtn then frame.removeBtn:Hide() end
                end
            end
        end
        -- Embedded panel slot rows
        local embedRow = self.embedSlotFrames[slot]
        if embedRow then
            local itemData = self.currentSet.items[slot]
            if itemData then
                embedRow.icon:SetTexture(itemData.icon)
                embedRow.itemName:SetText(itemData.name or "?")
            else
                embedRow.icon:SetTexture("Interface\\PaperDoll\\UI-Backpack-EmptySlot")
                embedRow.itemName:SetText("|cff666666(empty)|r")
            end
        end
    end
    if self.embedNameBox then
        self.embedNameBox:SetText(self.currentSet.name or "Unnamed Set")
    end
    if self.embedSourceBox then
        self.embedSourceBox:SetText(self.currentSet.source or "")
    end
end

function SB:UpdateSlotHighlight()
    if not self.slotFrames then return end
    for slot = 1, 9 do
        local frame = self.slotFrames[slot]
        if frame then
            local bg = frame:GetChildren() or frame.bg
            -- Highlight selected slot
        end
    end
end

-- =============================================================================
-- EXPORT
-- =============================================================================

function SB:UpdateExportText()
    if not self.exportBox then return end
    
    local text = self:GenerateExportText()
    self.exportBox:SetText(text)
end

function SB:GenerateExportText()
    local set = self.currentSet
    local lines = {}
    
    table.insert(lines, "-- " .. set.name)
    table.insert(lines, "{")
    table.insert(lines, string.format('    setID = "CUSTOM_%s",', 
        set.name:upper():gsub("%s+", "_"):gsub("[^%w_]", "")))
    table.insert(lines, string.format('    name = "%s",', set.name))
    table.insert(lines, string.format('    armorType = "%s",', set.armorType or "UNKNOWN"))
    table.insert(lines, string.format('    source = "%s",', set.source or "Unknown"))
    table.insert(lines, string.format('    expansion = %d,', set.expansion or 0))
    table.insert(lines, '    faction = nil,')
    table.insert(lines, '    items = {')
    
    for slot = 1, 9 do
        local item = set.items[slot]
        if item then
            table.insert(lines, string.format('        [%d] = %d,     -- %s (%s)', 
                slot, item.itemID, item.name, SLOT_NAMES[slot]))
        end
    end
    
    table.insert(lines, '    },')
    table.insert(lines, '},')
    
    return table.concat(lines, "\n")
end

function SB:ExportCurrentSet()
    local text = self:GenerateExportText()
    
    -- Print to chat
    ETB:Print("|cff00ffff=== EXPORTED SET ===|r")
    for line in text:gmatch("[^\n]+") do
        ETB:Print(line)
    end
    ETB:Print("|cff00ffff=== END EXPORT ===|r")
    
    -- Also update the export box
    self:UpdateExportText()
    
    ETB:Print("|cff00ff00[Set Builder]|r Export complete! Copy from the Export panel or chat.")
end

function SB:SendToETB()
    if not ETB or not ETB.CustomSets then
        ETB:Print("|cffff0000[Set Builder]|r CustomSets not available.")
        return
    end
    
    local itemCount = 0
    for slot, item in pairs(self.currentSet.items) do
        if item then
            itemCount = itemCount + 1
        end
    end
    
    if itemCount == 0 then
        ETB:Print("|cffff0000[Set Builder]|r Cannot send empty set. Add some items first!")
        return
    end
    
    if not self.currentSet.name or self.currentSet.name == "" or self.currentSet.name == "Untitled Set" then
        ETB:Print("|cffff0000[Set Builder]|r Please name your set first!")
        return
    end
    
    local success, setID = ETB.CustomSets:ImportFromSetBuilder(self.currentSet)
    
    if success then
        ETB:Print("|cff00ff00[Set Builder]|r Set sent to ETB successfully!")
    else
        ETB:Print("|cffff0000[Set Builder]|r Failed to send set to ETB.")
    end
end

-- =============================================================================
-- SAVE/LOAD SETS
-- =============================================================================

function SB:SaveSet(name)
    local setCopy = {
        name = name,
        armorType = self.currentSet.armorType,
        source = self.currentSet.source,
        expansion = self.currentSet.expansion,
        faction = self.currentSet.faction,
        items = {},
    }
    
    for slot, item in pairs(self.currentSet.items) do
        setCopy.items[slot] = {
            itemID = item.itemID,
            name = item.name,
            icon = item.icon,
        }
    end
    
    ETB_SetBuilder_Data.savedSets[name] = setCopy
    
    ETB:Print(string.format("|cff00ff00[Set Builder]|r Saved set '%s'.", name))
    self:UpdateSavedSetsList()
end

function SB:LoadSet(name)
    local savedSet = ETB_SetBuilder_Data.savedSets[name]
    if not savedSet then
        ETB:Print(string.format("|cffff0000[Set Builder]|r Set '%s' not found.", name))
        return
    end
    
    self.currentSet = {
        name = savedSet.name,
        armorType = savedSet.armorType,
        source = savedSet.source,
        expansion = savedSet.expansion,
        faction = savedSet.faction,
        items = {},
    }
    
    for slot, item in pairs(savedSet.items) do
        self.currentSet.items[slot] = {
            itemID = item.itemID,
            name = item.name,
            icon = item.icon,
        }
    end
    
    -- Update UI
    if self.nameBox then self.nameBox:SetText(savedSet.name) end
    if self.sourceBox then self.sourceBox:SetText(savedSet.source or "") end
    ETBDropdown:SetText(_G["SBArmorTypeDropdown"], savedSet.armorType or "Auto-detect")
    ETBDropdown:SetText(_G["SBExpansionDropdown"], EXPANSION_NAMES[savedSet.expansion] or "Classic")
    
    self:UpdateSetDisplay()
    self:UpdateExportText()
    
    ETB:Print(string.format("|cff00ff00[Set Builder]|r Loaded set '%s'.", name))
end

function SB:DeleteSet(name)
    if ETB_SetBuilder_Data.savedSets[name] then
        ETB_SetBuilder_Data.savedSets[name] = nil
        ETB:Print(string.format("|cff00ff00[Set Builder]|r Deleted set '%s'.", name))
        self:UpdateSavedSetsList()
    else
        ETB:Print(string.format("|cffff0000[Set Builder]|r Set '%s' not found.", name))
    end
end

function SB:ListSavedSets()
    ETB:Print("|cff00ffff=== Saved Sets ===|r")
    local count = 0
    for name, set in pairs(ETB_SetBuilder_Data.savedSets) do
        local pieceCount = 0
        for _ in pairs(set.items) do pieceCount = pieceCount + 1 end
        ETB:Print(string.format("  %s (%d pieces)", name, pieceCount))
        count = count + 1
    end
    if count == 0 then
        ETB:Print("  (no saved sets)")
    end
end

function SB:UpdateSavedSetsList()
    if not self.savedScrollChild then return end
    
    -- Clear existing buttons
    for _, btn in ipairs(self.savedButtons) do
        btn:Hide()
    end
    
    local yOffset = 0
    local btnIdx = 0
    
    for name, set in pairs(ETB_SetBuilder_Data.savedSets) do
        btnIdx = btnIdx + 1
        
        local btn = self.savedButtons[btnIdx]
        if not btn then
            btn = CreateFrame("Button", nil, self.savedScrollChild)
            btn:SetSize(190, 20)
            btn:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD")
            
            local text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            text:SetPoint("LEFT", 5, 0)
            text:SetWidth(150)
            text:SetJustifyH("LEFT")
            btn.text = text
            
            self.savedButtons[btnIdx] = btn
        end
        
        btn:SetPoint("TOPLEFT", 0, -yOffset)
        btn.text:SetText(name)
        btn.setName = name
        btn:SetScript("OnClick", function()
            SB:LoadSet(name)
        end)
        btn:Show()
        
        yOffset = yOffset + 22
    end
    
    self.savedScrollChild:SetHeight(math.max(yOffset, 1))
end

-- =============================================================================
-- PANEL MODE (full panel in main addon content area, like Armor/Weapons/Sets)
-- =============================================================================

SB.panelFrame = nil
SB.panelContent = nil

function SB:ShowAsPanel(parent)
    if not parent then return end
    if not self.panelFrame or self.panelFrame:GetParent() ~= parent then
        self:CreatePanelUI(parent)
    end
    -- Point main refs at panel elements so UpdateSetDisplay, FilterItems, etc. work
    self.mannequin = self.panelMannequin
    self.mannequinPanel = self.panelMannequinPanel
    self.nameBox = self.panelNameBox
    self.sourceBox = self.panelSourceBox
    self.slotFrames = self.panelSlotFrames
    self.setPanel = self.panelSetPanel
    self.searchBox = self.panelSearchBox
    self.slotDropdown = self.panelSlotDropdown
    self.statsText = self.panelStatsText
    self.scrollFrame = self.panelScrollFrame
    self.scrollChild = self.panelScrollChild
    self.itemButtons = self.panelItemButtons
    self.prevBtn = self.panelPrevBtn
    self.nextBtn = self.panelNextBtn
    self.pageText = self.panelPageText
    self.browserPanel = self.panelBrowserPanel
    self.exportBox = self.panelExportBox
    self.exportPanel = self.panelExportPanel
    self.frame = self.panelFrame
    self.panelFrame:Show()
    if not self.loadingComplete then
        self:LoadAllItems()
    else
        self:FilterItems()
        self:UpdateSetDisplay()
        self:UpdateExportText()
        if self.mannequin then self:UpdateMannequin() end
    end
end

function SB:HidePanel()
    if self.panelFrame then
        self.panelFrame:Hide()
    end
    -- Clear frame ref so full window can be used
    if self.frame == self.panelFrame then
        self.frame = nil
    end
end

function SB:CreatePanelUI(parent)
    local panel = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    panel:SetAllPoints(parent)
    panel:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    local scroll = CreateFrame("ScrollFrame", nil, panel, "UIPanelScrollFrameTemplate")
    scroll:SetAllPoints(panel)
    local content = CreateFrame("Frame", nil, scroll)
    content:SetSize(1300, 780)
    scroll:SetScrollChild(content)
    -- Reuse full UI creation; then save refs for panel
    self:CreateMannequinPanel(content)
    self.panelMannequin = self.mannequin
    self.panelMannequinPanel = self.mannequinPanel
    self:CreateSetPanel(content)
    self.panelNameBox = self.nameBox
    self.panelSourceBox = self.sourceBox
    self.panelSlotFrames = self.slotFrames
    self.panelSetPanel = self.setPanel
    self:CreateItemBrowser(content)
    self.panelSearchBox = self.searchBox
    self.panelSlotDropdown = self.slotDropdown
    self.panelStatsText = self.statsText
    self.panelScrollFrame = self.scrollFrame
    self.panelScrollChild = self.scrollChild
    self.panelItemButtons = self.itemButtons
    self.panelPrevBtn = self.prevBtn
    self.panelNextBtn = self.nextBtn
    self.panelPageText = self.pageText
    self.panelBrowserPanel = self.browserPanel
    -- Skip Export/code panel in panel view (use /sb export to copy set)
    self.panelExportBox = nil
    self.panelExportPanel = nil
    self.panelFrame = panel
    self.panelContent = content
end

-- Alias for SetsUI and slash: Open = Show
ETB.SetBuilder.Open = ETB.SetBuilder.Show
