-- EmoteMenuUI.lua
-- UI, rendering, interactions for EmoteMenu. Core lives in EmoteMenu.lua

local ADDON_NAME, EmoteMenu = ...
local DB = EmoteMenu.DB

-- UI constants/state
local SHOW_ALL_INDEX = 0
local FAVORITES_INDEX = -1
local SHOW_CATEGORY_SIDEBAR = false
local FRAME_WIDTH_WITH_SIDEBAR = 600
local FRAME_WIDTH_NO_SIDEBAR = 430
local FRAME_HEIGHT = 620
local ICONS_PATH = "Interface/AddOns/EmoteMenu/Icons/"

-- Filters (persisted): tri-state per attribute and a combine mode
-- mode: -1 = exclude, 0 = off, 1 = include; combine: "and" | "or"
local filterSoundMode, filterAnimMode, filterCombine, searchText = 0, 0, "or", ""

-- Sort mode (persisted): "custom" | "default" | "az" | "za"
local sortMode = "custom"
local function LoadSortMode()
    EmoteMenuSaved = EmoteMenuSaved or {}
    local m = EmoteMenuSaved.sortMode
    if m == "custom" or m == "default" or m == "az" or m == "za" then
        sortMode = m
    else
        sortMode = "custom"
    end
end
local function SaveSortMode()
    EmoteMenuSaved = EmoteMenuSaved or {}
    EmoteMenuSaved.sortMode = sortMode
end

-- Default selection to "All" unless already set (persist across reloads if UI retains state)
EmoteMenu.selectedCategory = EmoteMenu.selectedCategory or SHOW_ALL_INDEX

-- UI settings (persisted): sidebar toggle
local function LoadUISettings()
    EmoteMenuSaved = EmoteMenuSaved or {}
    if EmoteMenuSaved.showSidebar ~= nil then
        SHOW_CATEGORY_SIDEBAR = EmoteMenuSaved.showSidebar and true or false
    end
end

local function SaveUISettings()
    EmoteMenuSaved = EmoteMenuSaved or {}
    EmoteMenuSaved.showSidebar = SHOW_CATEGORY_SIDEBAR and true or false
    -- Persist selected category (store index and name for resilience)
    local sel = EmoteMenu.selectedCategory or SHOW_ALL_INDEX
    local name = nil
    if sel ~= 0 and sel ~= -1 and (DB.categories and DB.categories[sel]) then
        name = DB.categories[sel].name
    end
    EmoteMenuSaved.selectedCategory = { idx = sel, name = name }
end

-- Resolve selected category from SavedVariables after DB is ready
local function LoadSelectedCategory()
    EmoteMenuSaved = EmoteMenuSaved or {}
    local saved = EmoteMenuSaved.selectedCategory
    local resolved = SHOW_ALL_INDEX
    if type(saved) == "table" then
        local idx = tonumber(saved.idx)
        local name = saved.name
        if idx and DB.categories and DB.categories[idx] then
            resolved = idx
        elseif name and DB.categories then
            for i, cat in ipairs(DB.categories) do
                if cat.name == name then resolved = i; break end
            end
        end
    elseif type(saved) == "number" then
        if DB.categories and DB.categories[saved] then resolved = saved end
    end
    EmoteMenu.selectedCategory = resolved
end

-- Launcher settings (mode and positions)
local function LoadLauncherSettings()
    EmoteMenuSaved = EmoteMenuSaved or {}
    EmoteMenuSaved.launcher = EmoteMenuSaved.launcher or {}
    local L = EmoteMenuSaved.launcher
    L.mode = L.mode or "chat" -- chat | floating | none
    if L.mode ~= "chat" and L.mode ~= "floating" and L.mode ~= "none" then L.mode = "chat" end
    L.floatPos = L.floatPos or { point = "CENTER", rel = "UIParent", relPoint = "CENTER", x = 0, y = 0 }
    return L
end

local function HideAllLaunchers()
    if _G.EmoteMenuLauncherButton then _G.EmoteMenuLauncherButton:Hide() end
    if _G.EmoteMenuLauncherFloat then _G.EmoteMenuLauncherFloat:Hide() end
end

local function EnsureChatLauncher()
    -- Reuse existing creation logic
    CreateLauncherButton()
    if _G.EmoteMenuLauncherButton then _G.EmoteMenuLauncherButton:Show() end
end

local function EnsureFloatingLauncher(L)
    local btn = _G.EmoteMenuLauncherFloat
    if not btn then
        btn = CreateFrame("Button", "EmoteMenuLauncherFloat", UIParent, "BackdropTemplate")
        btn:SetSize(28, 28)
        CreateBackdrop(btn)
        btn.icon = btn:CreateTexture(nil, "ARTWORK"); btn.icon:SetAllPoints(true)
        if not TrySetIcon(btn.icon, "emotemenubutton") then btn.icon:SetTexture("Interface/Icons/INV_Misc_QuestionMark"); btn.icon:SetTexCoord(0.07,0.93,0.07,0.93) end
        btn:SetHighlightTexture("Interface/Buttons/ButtonHilight-Square", "ADD")
        btn:RegisterForClicks("LeftButtonUp")
        btn:SetScript("OnClick", function() if EmoteMenu.Toggle then EmoteMenu.Toggle() end end)
        btn:SetMovable(true); btn:SetClampedToScreen(true)
        btn:RegisterForDrag("LeftButton")
        btn:SetScript("OnDragStart", function(self)
            if IsShiftKeyDown() then self:StartMoving() end
        end)
        btn:SetScript("OnDragStop", function(self)
            self:StopMovingOrSizing()
            local p, rel, rp, x, y = self:GetPoint()
            rel = rel and rel:GetName() or "UIParent"
            EmoteMenuSaved.launcher.floatPos = { point = p or "CENTER", rel = rel, relPoint = rp or "CENTER", x = x or 0, y = y or 0 }
        end)
        btn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            if GameTooltip.ClearLines then GameTooltip:ClearLines() end
            GameTooltip:AddLine("Emote Menu", 1, 1, 1)
            GameTooltip:AddLine("Shift+Drag to move", 0.7, 0.7, 0.7)
            GameTooltip:Show()
            local num = GameTooltip:NumLines()
            local line = _G["GameTooltipTextLeft" .. tostring(num)]
            local smallFont = _G.GameFontDisableSmall or _G.GameFontNormalSmall
            if line and smallFont and line.SetFontObject then
                line:SetFontObject(smallFont)
            end
        end)
        btn:SetScript("OnLeave", function() if GameTooltip:IsShown() then GameTooltip:Hide() end end)
    end
    btn:ClearAllPoints()
    local pos = (L and L.floatPos) or { point="CENTER", rel="UIParent", relPoint="CENTER", x=0, y=0 }
    local relFrame = _G[pos.rel] or UIParent
    btn:SetPoint(pos.point or "CENTER", relFrame, pos.relPoint or "CENTER", pos.x or 0, pos.y or 0)
    btn:Show()
end

local function EnsureLauncherByMode()
    local L = LoadLauncherSettings()
    HideAllLaunchers()
    if L.mode == "chat" then
        EnsureChatLauncher()
    elseif L.mode == "floating" then
        EnsureFloatingLauncher(L)
    else
        -- none
    end
end

function EmoteMenu.SetLauncherMode(mode)
    local L = LoadLauncherSettings()
    if mode ~= "chat" and mode ~= "floating" and mode ~= "none" then return end
    L.mode = mode
    if mode == "floating" then
        -- reset floating position to center as requested
        L.floatPos = { point = "CENTER", rel = "UIParent", relPoint = "CENTER", x = 0, y = 0 }
    end
    EnsureLauncherByMode()
end

function EmoteMenu.GetLauncherMode()
    local L = LoadLauncherSettings(); return L.mode
end

local function LoadFilters()
    EmoteMenuSaved = EmoteMenuSaved or {}
    local f = EmoteMenuSaved.filters or {}
    -- Backward compatibility: old booleans mapped to include
    if type(f.soundMode) == "number" then
        filterSoundMode = (f.soundMode == 1 and 1) or (f.soundMode == -1 and -1) or 0
    else
        filterSoundMode = f.sound and 1 or 0
    end
    if type(f.animMode) == "number" then
        filterAnimMode = (f.animMode == 1 and 1) or (f.animMode == -1 and -1) or 0
    else
        filterAnimMode = f.anim and 1 or 0
    end
    -- Default to OR when not specified; respect explicit AND
    if f.combine == "and" then filterCombine = "and" else filterCombine = "or" end
    searchText = f.search or ""
end

local function SaveFilters()
    EmoteMenuSaved = EmoteMenuSaved or {}
    EmoteMenuSaved.filters = {
        soundMode = filterSoundMode,
        animMode = filterAnimMode,
        combine = filterCombine,
        search = searchText,
    }
end

local function matchesFilter(info)
    -- Exclusions always apply (AND)
    if filterSoundMode == -1 and info.sound then return false end
    if filterAnimMode == -1 and info.animation then return false end

    -- Includes can be combined via AND/OR
    local includeCount = 0
    local includeAny = false
    local includeAll = true
    if filterSoundMode == 1 then
        includeCount = includeCount + 1
        includeAny = includeAny or not not info.sound
        includeAll = includeAll and not not info.sound
    end
    if filterAnimMode == 1 then
        includeCount = includeCount + 1
        includeAny = includeAny or not not info.animation
        includeAll = includeAll and not not info.animation
    end
    if includeCount > 0 then
        if filterCombine == "or" then
            if not includeAny then return false end
        else -- and
            if not includeAll then return false end
        end
    end

    -- Text search
    if searchText and searchText ~= "" then
        local q = string.lower(searchText)
        local lbl = string.lower(tostring(info.label or ""))
        local cmd = string.lower(tostring(info.cmd or ""))
        if not string.find(lbl, q, 1, true) and not string.find(cmd, q, 1, true) then
            return false
        end
    end
    return true
end

function TrySetIcon(texture, cmd)
    if not texture or not cmd then return false end
    cmd = cmd:lower()
    -- Constrain to .tga only and avoid relying on SetTexture's return value
    texture:SetTexture(nil)
    texture:SetTexture(ICONS_PATH .. cmd .. ".tga")
    local tex = texture:GetTexture()
    return tex and true or false
end

function CreateBackdrop(frame)
    if frame.SetBackdrop then
        frame:SetBackdrop({
            bgFile = "Interface/FrameGeneral/UI-Background-Rock",
            edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
            tile = true, tileSize = 256, edgeSize = 16,
            insets = { left = 4, right = 4, top = 4, bottom = 4 }
        })
        if frame.SetBackdropColor then frame:SetBackdropColor(0.4, 0.4, 0.4, 0.90) end
        if frame.SetBackdropBorderColor then frame:SetBackdropBorderColor(0.8, 0.8, 0.8, 1) end
    end
end

local function SetDrag(frame)
    frame:EnableMouse(true)
    frame:SetMovable(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        self._moved = true
    end)
end

-- Drag ghost
local dragGhost
local function EnsureDragGhost()
    if dragGhost then return dragGhost end
    local f = CreateFrame("Frame", nil, UIParent)
    f:SetFrameStrata("TOOLTIP")
    f:SetSize(36, 36)
    f:EnableMouse(false)
    f:Hide()
    f.icon = f:CreateTexture(nil, "ARTWORK")
    f.icon:SetAllPoints(true)
    f.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
    dragGhost = f
    return dragGhost
end

local function ShowDragGhost(cmd)
    local f = EnsureDragGhost()
    if not TrySetIcon(f.icon, cmd) then
        f.icon:SetTexture("Interface/Icons/INV_Misc_QuestionMark")
        f.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
    end
    f:Show()
    f:SetScript("OnUpdate", function(self)
        local x, y = GetCursorPosition()
        local s = UIParent:GetEffectiveScale()
        x, y = x / s, y / s
        self:ClearAllPoints()
        self:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y)
    end)
end

local function HideDragGhost()
    if not dragGhost then return end
    dragGhost:SetScript("OnUpdate", nil)
    dragGhost:Hide()
end

-- UI locals
local mainFrame, leftPanelRef, rightPanelRef, categoryContent, emoteScroll, emoteContent
local categoryButtons, emoteButtons, sectionHeaders = {}, {}, {}

-- Default order per category captured before applying custom order
local defaultOrderByCategory = {}

-- Debounced render to avoid excessive layouts during rapid input
local renderPending = false
local function DebouncedRenderEmotes()
    if not mainFrame or not mainFrame.RenderEmotes then return end
    if not C_Timer or not C_Timer.After then mainFrame.RenderEmotes(); return end
    if renderPending then return end
    renderPending = true
    C_Timer.After(0.08, function()
        renderPending = false
        if mainFrame and mainFrame.RenderEmotes then mainFrame.RenderEmotes() end
    end)
end

-- Public access for UI toggle
EmoteMenu.Toggle = EmoteMenu.Toggle -- placeholder for assignment later

-- Forward declaration so ApplySidebarLayout can call it before it's defined
local RenderCategories

-- Shared layout applier for sidebar state
local function ApplySidebarLayout()
    if not mainFrame or not rightPanelRef then return end
    if SHOW_CATEGORY_SIDEBAR then
        mainFrame:SetWidth(FRAME_WIDTH_WITH_SIDEBAR)
        if leftPanelRef then leftPanelRef:Show() end
        rightPanelRef:ClearAllPoints()
        if leftPanelRef then
            rightPanelRef:SetPoint("TOPLEFT", leftPanelRef, "TOPRIGHT", 10, 0)
        else
            rightPanelRef:SetPoint("TOPLEFT", 10, -40)
        end
        rightPanelRef:SetPoint("BOTTOMRIGHT", -10, 10)
        RenderCategories(EmoteMenu.selectedCategory)
        if mainFrame._sidebarBtn then mainFrame._sidebarBtn:SetText("<<") end
    else
        mainFrame:SetWidth(FRAME_WIDTH_NO_SIDEBAR)
        if leftPanelRef then leftPanelRef:Hide() end
        rightPanelRef:ClearAllPoints()
        rightPanelRef:SetPoint("TOPLEFT", 10, -40)
        rightPanelRef:SetPoint("BOTTOMRIGHT", -10, 10)
        if mainFrame._sidebarBtn then mainFrame._sidebarBtn:SetText(">>") end
    end
    if type(SaveUISettings) == "function" then pcall(SaveUISettings) end
    if mainFrame.RenderEmotes then mainFrame.RenderEmotes() end
end

-- Launcher
CreateLauncherButton = function()
    if _G.EmoteMenuLauncherButton then return end
    local anchor = _G.ChatFrameMenuButton
    local btn = CreateFrame("Button", "EmoteMenuLauncherButton", UIParent)
    btn:SetSize(26, 26)
    if anchor then
        btn:SetPoint("BOTTOM", anchor, "TOP", 0, 6)
        btn:SetFrameStrata("HIGH")
    else
        if _G.ChatFrame1 then
            btn:SetPoint("BOTTOMLEFT", _G.ChatFrame1, "TOPLEFT", 0, 6)
        else
            btn:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 10, 140)
        end
    end
    btn.icon = btn:CreateTexture(nil, "ARTWORK")
    btn.icon:SetAllPoints(true)
    if not TrySetIcon(btn.icon, "emotemenubutton") then
        btn.icon:SetTexture("Interface/Icons/INV_Misc_QuestionMark")
        btn.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
    end
    btn:SetHighlightTexture("Interface/Buttons/ButtonHilight-Square", "ADD")
    btn:RegisterForClicks("LeftButtonUp")
    btn:SetScript("OnClick", function()
        if EmoteMenu.Toggle then EmoteMenu.Toggle() end
    end)
    btn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        if GameTooltip.ClearLines then GameTooltip:ClearLines() end
        GameTooltip:AddLine("Emote Menu", 1, 1, 1)
        GameTooltip:Show()
    end)
    btn:SetScript("OnLeave", function()
        if GameTooltip:IsShown() then GameTooltip:Hide() end
    end)
    btn:Show()
end

-- Categories panel
RenderCategories = function(selectedCategory)
    -- Reuse pooled buttons: show/reset those needed, hide the rest
    local function getOrCreateBtn(idx)
        local btn = categoryButtons[idx]
        if not btn then
            btn = CreateFrame("Button", nil, categoryContent, "UIPanelButtonTemplate")
            btn:SetSize(140, 24)
            categoryButtons[idx] = btn
        end
        btn:Show()
        return btn
    end

    -- First hide all; we will re-show what's used and hide the remainder at the end
    for _, b in ipairs(categoryButtons) do b:Hide() end

    local used = 0
    local function placeButton(text, onClick, isDisabled, y)
        used = used + 1
        local btn = getOrCreateBtn(used)
        btn:ClearAllPoints()
        btn:SetPoint("TOPLEFT", 8, y)
        btn:SetText(text)
        btn:SetScript("OnClick", onClick)
        if isDisabled then btn:Disable() else btn:Enable() end
    end

    local y = -8
    -- All
    placeButton("All", function()
        EmoteMenu.selectedCategory = SHOW_ALL_INDEX
        SaveUISettings()
        RenderCategories(EmoteMenu.selectedCategory)
        if mainFrame and mainFrame.RenderEmotes then mainFrame.RenderEmotes() end
    end, selectedCategory == SHOW_ALL_INDEX, y)
    y = y - 28

    -- Favorites
    placeButton("Favorites", function()
        EmoteMenu.selectedCategory = FAVORITES_INDEX
        SaveUISettings()
        RenderCategories(EmoteMenu.selectedCategory)
        if mainFrame and mainFrame.RenderEmotes then mainFrame.RenderEmotes() end
    end, selectedCategory == FAVORITES_INDEX, y)
    y = y - 28

    -- Real categories
    for i, cat in ipairs(DB.categories or {}) do
        local idx = i -- capture
        placeButton(cat.name or ("Category %d"):format(i), function()
            EmoteMenu.selectedCategory = idx
            SaveUISettings()
            RenderCategories(EmoteMenu.selectedCategory)
            if mainFrame and mainFrame.RenderEmotes then mainFrame.RenderEmotes() end
        end, idx == selectedCategory, y)
        y = y - 28
    end

    -- Hide any leftover pooled buttons
    for i = used + 1, #categoryButtons do
        categoryButtons[i]:Hide()
    end
end

-- Helpers
local function getCategoryEmotes()
    if EmoteMenu.selectedCategory == FAVORITES_INDEX then
        local list = {}
        for _, cat in ipairs(DB.categories or {}) do
            for _, e in ipairs(cat.emotes or {}) do
                if EmoteMenu.favoritesSet[e.cmd] then table.insert(list, e) end
            end
        end
        return list
    end
    local cat = (DB.categories and DB.categories[EmoteMenu.selectedCategory])
    if not cat then return {} end
    return cat.emotes or {}
end

local emoteButtonsCache = {}
local function CreateOrGetEmoteButton(index)
    local btn = emoteButtons[index]
    if not btn then
        btn = CreateFrame("Button", nil, emoteContent, "BackdropTemplate")
        btn:SetFrameStrata("DIALOG")
        btn:SetSize(100, 28)
        CreateBackdrop(btn)
        btn.icon = btn:CreateTexture(nil, "ARTWORK")
        btn.icon:SetSize(32, 32)
        btn.icon:SetPoint("LEFT", 4, 0)
        btn.label = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        btn.label:SetPoint("LEFT", btn.icon, "RIGHT", 6, 0)
        btn.label:SetPoint("RIGHT", -6, 0)
        btn.label:SetJustifyH("LEFT")
        btn:SetScript("OnEnter", function(self)
            if not self.tooltipBase then return end
            local cmd = self._cmd
            local lines = { self.tooltipBase }
            if cmd and EmoteMenu.hiddenSet[cmd] then
                table.insert(lines, "|cffaaaaaaAlt-Click to unhide|r")
            else
                table.insert(lines, "|cffaaaaaaAlt-Click to hide|r")
            end
            table.insert(lines, "|cffaaaaaaRight-Click to toggle favorite|r")
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(table.concat(lines, "\n"))
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function()
            if GameTooltip:IsOwned(btn) then GameTooltip:Hide() end
        end)
        emoteButtons[index] = btn
    end
    btn:Show()
    return btn
end

local function mainFrame_RenderEmotes()
    for _, b in ipairs(emoteButtons) do b:Hide() end
    for _, h in ipairs(sectionHeaders) do h:Hide() end

    local spacingX, spacingY = 4, 4
    local startX, startY = 8, -8
    local btnW, btnH = 38, 38
    local panelWidth = rightPanelRef and rightPanelRef:GetWidth() or (SHOW_CATEGORY_SIDEBAR and 600 or 430) - 20
    local cols = math.max(3, math.floor((panelWidth - startX) / (btnW + spacingX)))

    local row, col = 0, 0
    local btnIndex = 0
    local gridIndexInfo = {}
    local currentSection = nil
    local currentCatName = nil

    local function placeHeader(text)
        local idx
        for i, h in ipairs(sectionHeaders) do if not h:IsShown() then idx = i; break end end
        local hdr = idx and sectionHeaders[idx] or emoteContent:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
        if not idx then table.insert(sectionHeaders, hdr) end
        hdr:Show()
        hdr:ClearAllPoints()
        hdr:SetPoint("TOPLEFT", startX, startY - row * (btnH + spacingY) + 2)
        hdr:SetText(text)
        local headerGapRows = 0.35
        row = row + headerGapRows
        col = 0
    end

    local function placeButton(info)
        btnIndex = btnIndex + 1
        local btn = CreateOrGetEmoteButton(btnIndex)
        btn:SetSize(btnW, btnH)
        local x = startX + col * (btnW + spacingX)
        local y = startY - row * (btnH + spacingY)
        btn:ClearAllPoints()
        btn:SetPoint("TOPLEFT", x, y)
        local labelText = info.label or info.cmd
        btn.tooltipBase = labelText
        btn._cmd = info.cmd
        btn._section = currentSection
        btn._catName = currentCatName
        btn._gridIndex = btnIndex
        gridIndexInfo[btnIndex] = { section = currentSection, cat = currentCatName, cmd = info.cmd, info = info }
        local hasIcon = TrySetIcon(btn.icon, info.cmd)
        if not hasIcon then
            btn.icon:SetTexture(nil)
            btn.label:Show(); btn.label:SetText(labelText)
            btn.label:ClearAllPoints(); btn.label:SetPoint("CENTER")
        else
            btn.label:SetText(""); btn.label:Hide()
        end
        if EmoteMenu.favoritesSet[info.cmd] then
            if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(1, 0.84, 0, 1) end
        else
            if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(0.6, 0.6, 0.6, 1) end
        end
        local isHidden = EmoteMenu.hiddenSet[info.cmd] and true or false
        if isHidden and IsAltKeyDown() then
            btn:SetAlpha(0.35)
            if btn.icon then btn.icon:SetDesaturated(true) end
            if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(0.35, 0.35, 0.35, 1) end
        else
            btn:SetAlpha(1)
            if btn.icon then btn.icon:SetDesaturated(false) end
        end
        btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
        btn:SetScript("OnClick", function(self, mouseButton)
            if mouseButton == "RightButton" then
                if EmoteMenu.favoritesSet[info.cmd] then
                    EmoteMenu.favoritesSet[info.cmd] = nil
                    for i = #EmoteMenu.favorites, 1, -1 do if EmoteMenu.favorites[i] == info.cmd then table.remove(EmoteMenu.favorites, i) end end
                else
                    EmoteMenu.favoritesSet[info.cmd] = true
                    table.insert(EmoteMenu.favorites, info.cmd)
                end
                EmoteMenu.SaveFavorites()
                mainFrame.RenderEmotes(); RenderCategories(EmoteMenu.selectedCategory)
                return
            end
            if IsAltKeyDown() then
                if EmoteMenu.hiddenSet[info.cmd] then
                    EmoteMenu.hiddenSet[info.cmd] = nil
                    for i = #EmoteMenu.hidden, 1, -1 do if EmoteMenu.hidden[i] == info.cmd then table.remove(EmoteMenu.hidden, i) end end
                else
                    EmoteMenu.hiddenSet[info.cmd] = true
                    table.insert(EmoteMenu.hidden, info.cmd)
                end
                EmoteMenu.SaveHidden(); mainFrame.RenderEmotes()
            else
                EmoteMenu.RunEmote(info.cmd)
            end
        end)
        btn:EnableMouse(true); btn:RegisterForDrag("LeftButton"); btn:SetMovable(true)
        btn:SetScript("OnDragStart", function(self)
            if IsAltKeyDown() then return end
            self._dragging = true; self._dragSourceIndex = self._gridIndex; self:SetAlpha(0.6)
            if self._cmd then ShowDragGhost(self._cmd) end
        end)
        btn:SetScript("OnDragStop", function(self)
            if not self._dragging then return end
            self._dragging = false; self:SetAlpha(1); HideDragGhost()
            local cx, cy = GetCursorPosition(); local uiScale = UIParent:GetEffectiveScale(); cx, cy = cx / uiScale, cy / uiScale
            local dropIndex
            for i = 1, btnIndex do
                local b = emoteButtons[i]
                if b and b:IsShown() then
                    local left, bottom, width, height = b:GetLeft(), b:GetBottom(), b:GetWidth(), b:GetHeight()
                    if left and bottom and width and height then
                        local right, top = left + width, bottom + height
                        if cx >= left and cx <= right and cy >= bottom and cy <= top then dropIndex = i; break end
                    end
                end
            end
            if not dropIndex then mainFrame.RenderEmotes(); return end
            local src, dst = gridIndexInfo[self._dragSourceIndex], gridIndexInfo[dropIndex]
            if not src or not dst then mainFrame.RenderEmotes(); return end
            if src.section ~= dst.section then mainFrame.RenderEmotes(); return end
            if src.section == "category" and src.cat ~= dst.cat then mainFrame.RenderEmotes(); return end
            if src.section == "favorites" then
                local srcCmd, dstCmd = src.cmd, dst.cmd
                local srcPos, dstPos
                for i, c in ipairs(EmoteMenu.favorites) do
                    if c == srcCmd then srcPos = i end
                    if c == dstCmd then dstPos = i end
                end
                if not srcPos or not dstPos or srcPos == dstPos then mainFrame.RenderEmotes(); return end
                local moving = table.remove(EmoteMenu.favorites, srcPos)
                if dstPos > srcPos then dstPos = dstPos - 1 end
                table.insert(EmoteMenu.favorites, dstPos, moving)
                EmoteMenu.SaveFavorites(); mainFrame.RenderEmotes(); return
            elseif src.section == "category" then
                local cat
                for _, c in ipairs(DB.categories or {}) do if c.name == src.cat then cat = c; break end end
                if not cat or not cat.emotes then mainFrame.RenderEmotes(); return end
                local rendered = {}
                for _, e in ipairs(cat.emotes) do
                    if (not EmoteMenu.hiddenSet[e.cmd] or IsAltKeyDown()) and matchesFilter(e) then table.insert(rendered, e) end
                end
                local srcEmote, dstEmote
                for _, item in ipairs(rendered) do if item.cmd == src.cmd then srcEmote = item end; if item.cmd == dst.cmd then dstEmote = item end end
                if not srcEmote or not dstEmote then mainFrame.RenderEmotes(); return end
                local posSrc, posDst
                for i, e in ipairs(cat.emotes) do if e == srcEmote then posSrc = i end; if e == dstEmote then posDst = i end end
                if not posSrc or not posDst or posSrc == posDst then mainFrame.RenderEmotes(); return end
                local moving = table.remove(cat.emotes, posSrc)
                if posDst > posSrc then posDst = posDst - 1 end
                table.insert(cat.emotes, posDst, moving)
                EmoteMenu.SaveOrderForCategory(cat.name, cat); mainFrame.RenderEmotes(); return
            end
        end)
        col = col + 1; if col >= cols then col = 0; row = row + 1 end
    end

    local function tryPlaceButton(info)
        if EmoteMenu.hiddenSet[info.cmd] and not IsAltKeyDown() then return false end
        if not matchesFilter(info) then return false end
        placeButton(info); return true
    end

    local function sectionHasMatches(list, excludeFavorites)
        for _, info in ipairs(list or {}) do
            if (not excludeFavorites or not EmoteMenu.favoritesSet[info.cmd]) and matchesFilter(info) then return true end
        end
        return false
    end

    -- Sorting helpers
    local function getLabelText(info)
        return string.lower(tostring(info.label or info.cmd or ""))
    end
    local function sortedCopy(list, catName, excludeFavorites)
        local out = {}
        for _, info in ipairs(list or {}) do
            if not excludeFavorites or not EmoteMenu.favoritesSet[info.cmd] then table.insert(out, info) end
        end
        if sortMode == "default" then
            if catName and defaultOrderByCategory[catName] then
                local idxMap = defaultOrderByCategory[catName]
                table.sort(out, function(a, b)
                    local ia = idxMap[a.cmd] or math.huge
                    local ib = idxMap[b.cmd] or math.huge
                    if ia ~= ib then return ia < ib end
                    return getLabelText(a) < getLabelText(b)
                end)
            end
        elseif sortMode == "az" then
            table.sort(out, function(a, b) return getLabelText(a) < getLabelText(b) end)
        elseif sortMode == "za" then
            table.sort(out, function(a, b) return getLabelText(a) > getLabelText(b) end)
        else
            -- custom: keep current order as provided by DB (already has ApplyOrder)
        end
        return out
    end

    if EmoteMenu.selectedCategory == SHOW_ALL_INDEX then
        if #EmoteMenu.favorites > 0 then
            local favHasMatches = false
            for _, cmd in ipairs(EmoteMenu.favorites) do
                for _, cat in ipairs(DB.categories or {}) do
                    for _, info in ipairs(cat.emotes or {}) do
                        if info.cmd == cmd and matchesFilter(info) and (not EmoteMenu.hiddenSet[info.cmd] or IsAltKeyDown()) then favHasMatches = true; break end
                    end
                    if favHasMatches then break end
                end
                if favHasMatches then break end
            end
            if favHasMatches then
                placeHeader("Favorites"); currentSection = "favorites"; currentCatName = nil
                for _, cmd in ipairs(EmoteMenu.favorites) do
                    for _, cat in ipairs(DB.categories or {}) do
                        for _, info in ipairs(cat.emotes or {}) do
                            if info.cmd == cmd then if tryPlaceButton(info) then break end end
                        end
                    end
                end
                if col > 0 then col = 0; row = row + 1 end
            end
        end
        for i, cat in ipairs(DB.categories or {}) do
            if cat.emotes and #cat.emotes > 0 and sectionHasMatches(cat.emotes, true) then
                placeHeader(cat.name or ("Category %d"):format(i)); currentSection = "category"; currentCatName = cat.name
                local list = sortedCopy(cat.emotes, cat.name, true)
                for _, info in ipairs(list) do
                    if not EmoteMenu.favoritesSet[info.cmd] then tryPlaceButton(info) end
                end
                if col > 0 then col = 0; row = row + 1 end
            end
        end
    elseif EmoteMenu.selectedCategory == FAVORITES_INDEX then
        if #EmoteMenu.favorites == 0 then
            placeHeader("Favorites");
            local fs = emoteContent:CreateFontString(nil, "OVERLAY", "GameFontDisable")
            fs:SetPoint("TOPLEFT", startX, startY - row * (btnH + spacingY))
            fs:SetText("No favorites yet. Right-click an emote to add it.")
            table.insert(sectionHeaders, fs)
        else
            currentSection = "favorites"; currentCatName = nil
            local placed = 0
            for _, cmd in ipairs(EmoteMenu.favorites) do
                for _, cat in ipairs(DB.categories or {}) do
                    for _, info in ipairs(cat.emotes or {}) do
                        if info.cmd == cmd and matchesFilter(info) and (not EmoteMenu.hiddenSet[info.cmd] or IsAltKeyDown()) then
                            placeButton(info); placed = placed + 1; break
                        end
                    end
                end
            end
            if placed == 0 then
                local fs = emoteContent:CreateFontString(nil, "OVERLAY", "GameFontDisable")
                fs:SetPoint("TOPLEFT", startX, startY)
                fs:SetText("No favorites match filters.")
                table.insert(sectionHeaders, fs)
            end
        end
    else
        currentSection = "category"; currentCatName = (DB.categories and DB.categories[EmoteMenu.selectedCategory] and DB.categories[EmoteMenu.selectedCategory].name) or nil
    local emotes = getCategoryEmotes()
    local list = sortedCopy(emotes, currentCatName, false)
    local placed = 0
    for _, info in ipairs(list) do if tryPlaceButton(info) then placed = placed + 1 end end
        if placed == 0 then
            local fs = emoteContent:CreateFontString(nil, "OVERLAY", "GameFontDisable")
            fs:SetPoint("TOPLEFT", startX, startY)
            fs:SetText("No emotes match filters.")
            table.insert(sectionHeaders, fs)
        end
    end

    local totalRows = math.ceil(row + (col > 0 and 1 or 0))
    local contentW = math.max(1, (cols * btnW) + ((cols - 1) * spacingX) + 16)
    local contentH = math.max(1, totalRows * (btnH + spacingY) + 16)
    emoteContent:SetSize(contentW, contentH)
end

local function CreateUI()
    if mainFrame then return end
    mainFrame = CreateFrame("Frame", "EmoteMenuMainFrame", UIParent, "BackdropTemplate")
    -- Load persisted UI settings (e.g., sidebar visibility) before sizing
    LoadUISettings()
    local frameWidth = SHOW_CATEGORY_SIDEBAR and FRAME_WIDTH_WITH_SIDEBAR or FRAME_WIDTH_NO_SIDEBAR
    mainFrame:SetSize(frameWidth, FRAME_HEIGHT)
    mainFrame:SetPoint("CENTER")
    mainFrame:SetFrameStrata("DIALOG")
    mainFrame:SetToplevel(true)
    CreateBackdrop(mainFrame)
    SetDrag(mainFrame)
    if mainFrame.SetClampedToScreen then mainFrame:SetClampedToScreen(true) end
    mainFrame:Hide()
    mainFrame:SetScript("OnHide", function() HideDragGhost() end)
    table.insert(UISpecialFrames, "EmoteMenuMainFrame")

    -- Sidebar toggle button (to the left of the title)
    local sidebarBtn = CreateFrame("Button", nil, mainFrame, "UIPanelButtonTemplate")
    sidebarBtn:SetSize(22, 22)
    sidebarBtn:SetPoint("TOPLEFT", 10, -10)
    sidebarBtn:SetText(SHOW_CATEGORY_SIDEBAR and ">>" or "<<")

    local title = mainFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
    title:ClearAllPoints()
    title:SetPoint("LEFT", sidebarBtn, "RIGHT", 6, 0)
    title:SetPoint("TOP", 0, -12)
    title:SetText("Emote Menu")

    local close = CreateFrame("Button", nil, mainFrame, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", 0, 0)

    LoadFilters()

    -- Tri-state filter buttons (Sound/Animation)
    local function makeTriStateButton(name, parent, iconPath)
        local btn = CreateFrame("Button", name, parent, "BackdropTemplate")
        btn:SetSize(28, 28)
        CreateBackdrop(btn)
        btn.icon = btn:CreateTexture(nil, "ARTWORK")
        btn.icon:SetSize(28, 28)
        btn.icon:SetPoint("CENTER")
        btn.icon:SetTexture(iconPath)
        btn.mark = btn:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
        btn.mark:SetPoint("CENTER")
        btn.mark:SetText("")
        -- Make the overlay mark larger and crisp
        local fontFile = STANDARD_TEXT_FONT
        if not fontFile and GameFontNormal and GameFontNormal.GetFont then
            fontFile = select(1, GameFontNormal:GetFont())
        end
        if btn.mark.SetFont and fontFile then
            btn.mark:SetFont(fontFile, 22, "OUTLINE")
        end
        btn.mark:SetAlpha(0.95)
        btn:SetHighlightTexture("Interface/Buttons/ButtonHilight-Square", "ADD")
        return btn
    end

    local soundBtn = makeTriStateButton("EmoteMenuSoundFilter", mainFrame, ICONS_PATH .. "shout.tga")
    soundBtn:SetPoint("TOPLEFT", title, "TOPRIGHT", 8, 2)

    local animBtn = makeTriStateButton("EmoteMenuAnimFilter", mainFrame, ICONS_PATH .. "dance.tga")
    animBtn:SetPoint("LEFT", soundBtn, "RIGHT", 8, 0)

    -- Visual state update for tri-state and combine indicator via tooltip
    local function colorForMode(mode)
        if mode == 1 then return 0.2, 0.9, 0.2 -- green include
        elseif mode == -1 then return 0.9, 0.2, 0.2 -- red exclude
        else return 0.7, 0.7, 0.7 -- grey off
        end
    end
    local function updateTriStateVisuals()
        -- Sound
        local r, g, b = colorForMode(filterSoundMode)
        if soundBtn.SetBackdropBorderColor then soundBtn:SetBackdropBorderColor(r, g, b, 1) end
        soundBtn.icon:SetDesaturated(filterSoundMode == 0)
        soundBtn.icon:SetVertexColor(r, g, b)
        soundBtn.mark:SetText(filterSoundMode == -1 and "–" or (filterSoundMode == 0 and "×" or "+"))
        soundBtn.mark:SetTextColor(r, g, b)
        -- Anim
        r, g, b = colorForMode(filterAnimMode)
        if animBtn.SetBackdropBorderColor then animBtn:SetBackdropBorderColor(r, g, b, 1) end
        animBtn.icon:SetDesaturated(filterAnimMode == 0)
        animBtn.icon:SetVertexColor(r, g, b)
        animBtn.mark:SetText(filterAnimMode == -1 and "–" or (filterAnimMode == 0 and "×" or "+"))
        animBtn.mark:SetTextColor(r, g, b)
    end

    -- Initialize states from saved values
    updateTriStateVisuals()

    local function showFilterTooltip(self, label, mode)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:ClearLines()
        GameTooltip:AddLine(label, 1, 1, 1)
        local stateText = (mode == 1 and "Include") or (mode == -1 and "Exclude") or "Off"
        GameTooltip:AddLine("State: " .. stateText, 0.9, 0.9, 0.9)
        GameTooltip:AddLine("Left-Click: Off, Include, Exclude", 0.8, 0.8, 0.8)
        GameTooltip:AddLine("Right-Click: Toggle " .. (filterCombine == "or" and "OR" or "AND"), 0.8, 0.8, 0.8)
        GameTooltip:Show()
    end

    soundBtn:SetScript("OnEnter", function(self) showFilterTooltip(self, "Filter: Voice Line", filterSoundMode) end)
    soundBtn:SetScript("OnLeave", function() if GameTooltip:IsShown() then GameTooltip:Hide() end end)
    animBtn:SetScript("OnEnter", function(self) showFilterTooltip(self, "Filter: Animation", filterAnimMode) end)
    animBtn:SetScript("OnLeave", function() if GameTooltip:IsShown() then GameTooltip:Hide() end end)

    local function cycleMode(current)
        if current == 0 then return 1 elseif current == 1 then return -1 else return 0 end
    end
    local function onTriStateClick(which, button)
        if button == "RightButton" then
            filterCombine = (filterCombine == "and") and "or" or "and"
        else
            if which == "sound" then
                filterSoundMode = cycleMode(filterSoundMode)
            else
                filterAnimMode = cycleMode(filterAnimMode)
            end
        end
        SaveFilters(); updateTriStateVisuals(); mainFrame.RenderEmotes()
    end

    soundBtn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    animBtn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    soundBtn:SetScript("OnClick", function(self, btn) onTriStateClick("sound", btn) end)
    animBtn:SetScript("OnClick", function(self, btn) onTriStateClick("anim", btn) end)

    local searchBox = CreateFrame("EditBox", "EmoteMenuSearchBox", mainFrame, "SearchBoxTemplate")
    searchBox:SetSize(120, 40); searchBox:SetAutoFocus(false); searchBox:SetPoint("LEFT", animBtn, "RIGHT", 16, 0)
    if searchText and searchText ~= "" then searchBox:SetText(searchText); if type(SearchBoxTemplate_OnEditFocusGained) == "function" then SearchBoxTemplate_OnEditFocusLost(searchBox) end end
    searchBox:SetScript("OnTextChanged", function(self)
        if type(SearchBoxTemplate_OnTextChanged) == "function" then SearchBoxTemplate_OnTextChanged(self) end
        searchText = self:GetText() or ""
        SaveFilters()
        DebouncedRenderEmotes()
    end)
    searchBox:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
    searchBox:SetScript("OnEscapePressed", function(self) self:SetText(""); searchText = ""; SaveFilters(); self:ClearFocus(); mainFrame.RenderEmotes() end)

    -- Load sort mode selection
    LoadSortMode()
    -- Compact symbols for the sort button label
    local sortSymbols = { custom = "C", default = "D", az = "Az", za = "Za" }
    local sortOrder = { "custom", "default", "az", "za" }
    local sortBtn = CreateFrame("Button", "EmoteMenuSortModeButton", mainFrame, "UIPanelButtonTemplate")
    sortBtn:SetSize(28, 28)
    sortBtn:SetPoint("LEFT", searchBox, "RIGHT", 8, 0)
    local function refreshSortLabel()
        sortBtn:SetText(sortSymbols[sortMode] or sortMode)
    end
    refreshSortLabel()
    sortBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText("Sort emotes")
        GameTooltip:AddLine("Custom: your drag-and-drop order", 0.8, 0.8, 0.8)
        GameTooltip:AddLine("Default: original data order", 0.8, 0.8, 0.8)
        GameTooltip:AddLine("A-Z / Z-A: alphabetical", 0.8, 0.8, 0.8)
        GameTooltip:Show()
    end)
    sortBtn:SetScript("OnLeave", function()
        if GameTooltip:IsShown() then GameTooltip:Hide() end
    end)
    sortBtn:SetScript("OnClick", function()
        local idx = 1
        for i, m in ipairs(sortOrder) do if m == sortMode then idx = i break end end
        idx = idx + 1; if idx > #sortOrder then idx = 1 end
        sortMode = sortOrder[idx]
        SaveSortMode()
        refreshSortLabel()
        mainFrame.RenderEmotes()
    end)

    leftPanelRef = CreateFrame("Frame", nil, mainFrame, "BackdropTemplate")
    leftPanelRef:SetFrameStrata("DIALOG"); leftPanelRef:SetPoint("TOPLEFT", 10, -40); leftPanelRef:SetPoint("BOTTOMLEFT", 10, 10); leftPanelRef:SetWidth(160)
    CreateBackdrop(leftPanelRef)

    -- Category area (top), with an options strip docked at the bottom
    local categoryArea = CreateFrame("Frame", nil, leftPanelRef)
    categoryArea:SetPoint("TOPLEFT", 4, -4)
    categoryArea:SetPoint("BOTTOMRIGHT", -4, 30) -- leave space for options strip
    categoryContent = categoryArea

    -- Options strip (from EmoteMenuOptions)
    if EmoteMenu.Options and EmoteMenu.Options.Create then
        EmoteMenu.Options.Create(leftPanelRef)
    end

    local rightPanel = CreateFrame("Frame", nil, mainFrame, "BackdropTemplate")
    rightPanel:SetFrameStrata("DIALOG")
    rightPanel:SetPoint("BOTTOMRIGHT", -10, 10); CreateBackdrop(rightPanel); rightPanelRef = rightPanel

    emoteScroll = CreateFrame("ScrollFrame", nil, rightPanel, "UIPanelScrollFrameTemplate")
    emoteScroll:SetFrameStrata("DIALOG"); emoteScroll:SetPoint("TOPLEFT", 4, -4); emoteScroll:SetPoint("BOTTOMRIGHT", -26, 4)
    emoteContent = CreateFrame("Frame", nil, emoteScroll); emoteContent:SetSize(1, 1); emoteScroll:SetScrollChild(emoteContent)

    mainFrame.RenderEmotes = mainFrame_RenderEmotes

    EmoteMenu.LoadFavorites(); EmoteMenu.LoadHidden(); EmoteMenu.LoadOrder(); EmoteMenu.BuildOtherCategory()
    -- Capture default order per category BEFORE applying custom order so "Default" reflects original data
    defaultOrderByCategory = {}
    for _, cat in ipairs(DB.categories or {}) do
        if cat.name and cat.emotes then
            local idxMap = {}
            for i, e in ipairs(cat.emotes) do if e.cmd then idxMap[e.cmd] = i end end
            defaultOrderByCategory[cat.name] = idxMap
        end
    end
    EmoteMenu.ApplyOrderToAllCategories()
    -- Resolve selected category from SavedVariables now that DB is prepared
    LoadSelectedCategory()

    -- Wire toggle to shared layout applier
    mainFrame._sidebarBtn = sidebarBtn

    sidebarBtn:SetScript("OnClick", function()
        SHOW_CATEGORY_SIDEBAR = not SHOW_CATEGORY_SIDEBAR
        ApplySidebarLayout()
    end)

    ApplySidebarLayout()

    mainFrame:RegisterEvent("MODIFIER_STATE_CHANGED")
    mainFrame:SetScript("OnEvent", function(_, event)
        if event == "MODIFIER_STATE_CHANGED" then DebouncedRenderEmotes() end
    end)
end

local function PositionMenuDefault()
    local anchor = _G.EmoteMenuLauncherButton or _G.ChatFrameMenuButton or _G.ChatFrame1
    mainFrame:ClearAllPoints()
    if anchor == _G.ChatFrame1 then
        mainFrame:SetPoint("BOTTOMLEFT", anchor, "TOPLEFT", 10, 12)
    else
        mainFrame:SetPoint("BOTTOMLEFT", anchor, "TOPRIGHT", 6, 6)
    end
end

local function ResetMenuPositionToCenter()
    if not mainFrame then CreateUI() end
    mainFrame:ClearAllPoints()
    mainFrame:SetPoint("CENTER")
    mainFrame._moved = true
    mainFrame:Show()
    if type(print) == "function" then print("EmoteMenu: position reset to screen center.") end
end

EmoteMenu.Toggle = function()
    if not mainFrame then CreateUI() end
    EnsureLauncherByMode()
    if not mainFrame._moved then PositionMenuDefault() end
    if mainFrame:IsShown() then mainFrame:Hide() else mainFrame:Show() end
end

-- Slash and init
SLASH_EMOTEMENU1 = "/emotemenu"
SlashCmdList["EMOTEMENU"] = function(msg)
    msg = tostring(msg or ""):lower():gsub("^%s+", ""):gsub("%s+$", "")
    if msg == "resetpos" or msg == "reset" then
        ResetMenuPositionToCenter()
    else
        EmoteMenu.Toggle()
    end
end

local EmoteMenuInit = CreateFrame("Frame")
EmoteMenuInit:RegisterEvent("PLAYER_LOGIN")
EmoteMenuInit:RegisterEvent("PLAYER_ENTERING_WORLD")
EmoteMenuInit:SetScript("OnEvent", function(_, evt)
    if evt == "PLAYER_LOGIN" then
    if C_Timer and C_Timer.After then C_Timer.After(0.5, EnsureLauncherByMode) else EnsureLauncherByMode() end
        print("EmoteMenu loaded. Type /emotemenu to open.")
    elseif evt == "PLAYER_ENTERING_WORLD" then
    if C_Timer and C_Timer.After then C_Timer.After(2, function() EnsureLauncherByMode() end) else EnsureLauncherByMode() end
    end
end)

-- Expose helpers for options panel
EmoteMenu.ResetMenuPosition = ResetMenuPositionToCenter
EmoteMenu.ToggleSidebar = function()
    if not EmoteMenuMainFrame then return end
    -- Ensure mainFrame exists
    if not mainFrame then CreateUI() end
    SHOW_CATEGORY_SIDEBAR = not SHOW_CATEGORY_SIDEBAR
    ApplySidebarLayout()
end
