-- =============================================================================
-- ETB v12.54.0 — Modules/SetBuilder/Frame.lua
-- =============================================================================
-- Third and final SetBuilder submodule. Owns the UI lifecycle and frame
-- construction. After this extraction, SetBuilder.lua becomes a thin
-- orchestrator with delegators only.
--
-- Three responsibilities:
--
-- 1. LIFECYCLE:
--    Initialize, Show, Hide, ShowEmbedded, HideEmbedded, ShowAsPanel, HidePanel
--    Manage frame visibility, kick off LoadAllItems on first Show, and route
--    between standalone-window vs embedded-panel display modes.
--
-- 2. STANDALONE UI CONSTRUCTION:
--    CreateUI — creates the main 1300x780 window with backdrop, drag, ESC
--               handling, and OnHide cleanup
--    CreateMannequinPanel — the 3D model preview with rotate/undress controls
--    CreateSetPanel — current-set editor: name/source inputs, armor type
--                     dropdown, expansion dropdown, 9 slot frames, action
--                     buttons (Clear/Send/Export)
--    CreateItemBrowser — searchable/filterable scrollable item list with
--                        pagination
--    CreateExportPanel — copy-paste export box + saved sets list + Save button
--    UpdateMannequin — re-applies currentSet items to the model with the
--                      v12.34.0-proven SetUnit+Undress reset pattern
--
-- 3. EMBEDDED + PANEL UI CONSTRUCTION:
--    CreateEmbeddedUI — compact ETB sidebar panel
--    CreatePanelUI — full UI inside a parent frame (ShowAsPanel mode)
--
-- Public API (all take owner = SB singleton as first arg):
--   Frame:Initialize(owner)
--   Frame:Show(owner)
--   Frame:Hide(owner)
--   Frame:ShowEmbedded(owner, parent)
--   Frame:HideEmbedded(owner)
--   Frame:CreateEmbeddedUI(owner, parent)
--   Frame:CreateUI(owner)
--   Frame:CreateMannequinPanel(owner, parent)
--   Frame:UpdateMannequin(owner)
--   Frame:CreateSetPanel(owner, parent)
--   Frame:CreateItemBrowser(owner, parent)
--   Frame:CreateExportPanel(owner, parent)
--   Frame:ShowAsPanel(owner, parent)
--   Frame:HidePanel(owner)
--   Frame:CreatePanelUI(owner, parent)
--
-- Owner contract — fields read/written:
--   owner.frame, owner.standaloneFrame, owner.panelFrame
--   owner.embedPanel, owner.embedParent, owner.embedSlotFrames
--   owner.mannequin, owner.mannequinPanel
--   owner.nameBox, owner.sourceBox, owner.embedNameBox, owner.embedSourceBox
--   owner.setPanel, owner.slotFrames, owner.slotDropdown
--   owner.searchBox, owner.statsText, owner.pageText
--   owner.scrollFrame, owner.scrollChild, owner.itemButtons
--   owner.prevBtn, owner.nextBtn, owner.browserPanel
--   owner.exportPanel, owner.exportBox, owner.savedScrollChild, owner.savedButtons
--   owner.currentSet, owner.searchText, owner.filterSlot, owner.filterArmorType
--   owner.currentPage, owner.itemsPerPage, owner.filteredItems, owner.loadingComplete
--   owner.panelMannequin, owner.panelMannequinPanel, owner.panelNameBox,
--   owner.panelSourceBox, owner.panelSlotFrames, owner.panelSetPanel,
--   owner.panelSearchBox, owner.panelSlotDropdown, owner.panelStatsText,
--   owner.panelScrollFrame, owner.panelScrollChild, owner.panelItemButtons,
--   owner.panelPrevBtn, owner.panelNextBtn, owner.panelPageText,
--   owner.panelBrowserPanel, owner.panelExportBox, owner.panelExportPanel,
--   owner.panelContent
--
-- Owner-exposed file-locals:
--   owner.SLOT_NAMES, owner.EXPANSION_NAMES
--
-- Owner methods called (all delegate to other submodules or main file):
--   owner:LoadAllItems()  (Items)
--   owner:FilterItems()   (Items)
--   owner:DisplayItems()  (Items)
--   owner:AddToSet(item)  (Export)
--   owner:RemoveFromSlot(slot)  (Export)
--   owner:ClearCurrentSet()     (Export)
--   owner:UpdateSetDisplay()    (Export)
--   owner:UpdateExportText()    (Export)
--   owner:ExportCurrentSet()    (Export)
--   owner:SendToETB()           (Export)
--   owner:SaveSet(name)         (Export)
--   owner:UpdateMannequin()     (Frame, self-call)
--
-- Globals/Blizzard:
--   ETBDropdown (NS-stored)
--   CreateFrame, GameTooltip
--   _G.ETBMainFrame (re-show on Hide)
-- =============================================================================

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

NS.ETB.SetBuilder = NS.ETB.SetBuilder or ETB.SetBuilder or {}
local Frame = {}
NS.ETB.SetBuilder.Frame = Frame

-- =============================================================================
-- LIFECYCLE
-- =============================================================================

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

function Frame:Show(owner)
    -- Always use the standalone window (not the embedded panel)
    if not owner.standaloneFrame then
        Frame:CreateUI(owner)
        owner.standaloneFrame = owner.frame
    end

    -- Restore standalone refs if ShowAsPanel overwrote them
    owner.frame = owner.standaloneFrame

    owner.frame:Show()
    owner.frame:Raise()  -- v12.52.2: ensure on top of any other DIALOG frames
    owner:LoadAllItems()
end

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

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

function Frame:HideEmbedded(owner)
    if owner.embedPanel then
        owner.embedPanel:Hide()
    end
end

-- =============================================================================
-- EMBEDDED UI (compact panel inside main addon sidebar)
-- =============================================================================

function Frame:CreateEmbeddedUI(owner, parent)
    if owner.embedPanel and owner.embedPanel:GetParent() == parent then
        return
    end
    owner.embedParent = parent
    owner.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(owner.currentSet.name or "Unnamed Set")
    nameBox:SetScript("OnTextChanged", function(self)
        owner.currentSet.name = self:GetText()
        owner: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(owner.currentSet.source or "")
    sourceBox:SetScript("OnTextChanged", function(self)
        owner.currentSet.source = self:GetText()
        owner: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" .. (owner.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

        owner.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() owner: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() owner: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() owner:SendToETB() end)
    y = y + 26

    child:SetHeight(y)

    owner.embedPanel = panel
    owner.embedNameBox = nameBox
    owner.embedSourceBox = sourceBox
end

-- =============================================================================
-- STANDALONE UI CONSTRUCTION
-- =============================================================================

function Frame:CreateUI(owner)
    -- 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("DIALOG")  -- v12.52.2: was HIGH, conflicted with MainFrame's HIGH
    f:SetClampedToScreen(true)

    -- ESC key closes the window.
    -- v14.10.1: was OnKeyDown + pcall(SetPropagateKeyboardInput) which fired
    -- ADDON_ACTION_BLOCKED on MoP Classic 5.4.8 (the "taint-safe" comment was
    -- misguided — UISpecialFrames is the canonical, taint-safe pattern).
    if UISpecialFrames then
        local already = false
        for _, name in ipairs(UISpecialFrames) do
            if name == "ETB_SetBuilderFrame" then already = true; break end
        end
        if not already then tinsert(UISpecialFrames, "ETB_SetBuilderFrame") 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()
        if ETB and ETB._builderHidMainFrame then
            ETB._builderHidMainFrame = nil
            if _G.ETBMainFrame then
                _G.ETBMainFrame:Show()
            end
        end
    end)

    -- Create panels (Export/code panel hidden - use /sb export to copy set)
    Frame:CreateMannequinPanel(owner, f)
    Frame:CreateSetPanel(owner, f)
    Frame:CreateItemBrowser(owner, f)
    Frame:CreateExportPanel(owner, f)
    if owner.exportPanel then owner.exportPanel:Hide() end

    owner.frame = f
    owner.standaloneFrame = f
end

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

function Frame:CreateMannequinPanel(owner, 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()
        owner:UpdateMannequin()
    end)

    owner.mannequin = model
    owner.mannequinPanel = panel
end

function Frame:UpdateMannequin(owner)
    if not owner.mannequin then return end

    -- Reset model to player using v12.34.0-proven SetUnit + Undress pattern.
    -- (See v12.48.4/v12.48.5 audit history — `Undress` alone does not reliably
    -- clear TryOn'd items in MoP Classic 5.4.8.)
    owner.mannequin:SetUnit("player")
    owner.mannequin:Undress()

    -- Apply all items in the current set
    for slot, itemData in pairs(owner.currentSet.items) do
        if itemData and itemData.itemID then
            owner.mannequin:TryOn("item:" .. itemData.itemID)
        end
    end
end

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

function Frame:CreateSetPanel(owner, parent)
    local ETBDropdown = NS.ETBDropdown  -- Resolved at call time, not file-load time

    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)
        owner.currentSet.name = self:GetText()
        owner:UpdateExportText()
    end)
    nameBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    owner.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)
        owner.currentSet.source = self:GetText()
        owner:UpdateExportText()
    end)
    sourceBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    owner.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)
                owner.currentSet.armorType = (t == "Auto-detect") and nil or t
                owner: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 = owner.EXPANSION_NAMES[i]
            info.value = i
            info.func = function()
                ETBDropdown:SetText(expDropdown, owner.EXPANSION_NAMES[i])
                owner.currentSet.expansion = i
                owner: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
    owner.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" .. (owner.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()
            owner:RemoveFromSlot(slot)
        end)
        removeBtn:Hide()
        slotFrame.removeBtn = removeBtn

        slotFrame.slot = slot
        slotFrame:SetScript("OnClick", function()
            owner.filterSlot = slot
            ETBDropdown:SetText(owner.slotDropdown, owner.SLOT_NAMES[slot])
            owner:FilterItems()
        end)

        owner.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()
        owner: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()
        owner: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()
        owner:ExportCurrentSet()
    end)

    owner.setPanel = panel
end

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

function Frame:CreateItemBrowser(owner, parent)
    local ETBDropdown = NS.ETBDropdown  -- Resolved at call time

    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)
        owner.searchText = self:GetText()  -- Don't lowercase here, FilterItems handles it
        owner.currentPage = 1
        -- v15.43.0: debounce so typing filters once, not per keystroke.
        if not owner._sbSearchDebounce then
            owner._sbSearchDebounce = true
            C_Timer.After(0.18, function()
                owner._sbSearchDebounce = false
                owner:FilterItems()
            end)
        end
    end)
    searchBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    owner.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")
            owner.filterSlot = "all"
            owner.currentPage = 1
            owner:FilterItems()
        end
        ETBDropdown:AddButton(_items_, info)

        for slot = 1, 9 do
            info.text = owner.SLOT_NAMES[slot]
            info.value = slot
            info.func = function()
                ETBDropdown:SetText(slotDropdown, owner.SLOT_NAMES[slot])
                owner.filterSlot = slot
                owner.currentPage = 1
                owner:FilterItems()
            end
            ETBDropdown:AddButton(_items_, info)
        end
    end)
    owner.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)
                owner.filterArmorType = (filterValue == "All") and "all" or filterValue
                owner.currentPage = 1
                owner:FilterItems()
            end
            info.checked = (owner.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")
    owner.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)

    owner.scrollFrame = scrollFrame
    owner.scrollChild = scrollChild
    owner.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 owner.currentPage > 1 then
            owner.currentPage = owner.currentPage - 1
            owner:DisplayItems()
        end
    end)
    owner.prevBtn = prevBtn

    local pageText = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    pageText:SetPoint("BOTTOM", 0, 15)
    owner.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(#owner.filteredItems / owner.itemsPerPage)
        if owner.currentPage < totalPages then
            owner.currentPage = owner.currentPage + 1
            owner:DisplayItems()
        end
    end)
    owner.nextBtn = nextBtn

    owner.browserPanel = panel
end

-- =============================================================================
-- EXPORT PANEL (Right side - shows export text + saved sets)
-- =============================================================================

function Frame:CreateExportPanel(owner, 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)
    owner.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)
    owner.savedScrollChild = savedChild
    owner.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 = owner.currentSet.name
        if name and name ~= "" and name ~= "Unnamed Set" then
            owner:SaveSet(name)
        else
            ETB:Print("|cffff0000[Set Builder]|r Please name your set first!")
        end
    end)

    owner.exportPanel = panel
end

-- =============================================================================
-- PANEL MODE (full UI inside a parent frame, e.g. for SetsUI integration)
-- =============================================================================

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

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

function Frame:CreatePanelUI(owner, 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
    Frame:CreateMannequinPanel(owner, content)
    owner.panelMannequin = owner.mannequin
    owner.panelMannequinPanel = owner.mannequinPanel
    Frame:CreateSetPanel(owner, content)
    owner.panelNameBox = owner.nameBox
    owner.panelSourceBox = owner.sourceBox
    owner.panelSlotFrames = owner.slotFrames
    owner.panelSetPanel = owner.setPanel
    Frame:CreateItemBrowser(owner, content)
    owner.panelSearchBox = owner.searchBox
    owner.panelSlotDropdown = owner.slotDropdown
    owner.panelStatsText = owner.statsText
    owner.panelScrollFrame = owner.scrollFrame
    owner.panelScrollChild = owner.scrollChild
    owner.panelItemButtons = owner.itemButtons
    owner.panelPrevBtn = owner.prevBtn
    owner.panelNextBtn = owner.nextBtn
    owner.panelPageText = owner.pageText
    owner.panelBrowserPanel = owner.browserPanel
    -- Skip Export/code panel in panel view (use /sb export to copy set)
    owner.panelExportBox = nil
    owner.panelExportPanel = nil
    owner.panelFrame = panel
    owner.panelContent = content
end
