local API = rawget(_G, "LIDPANELS_API")
local INTERNAL_API = type(API) == "table" and type(API.__internal) == "table" and API.__internal or nil
if type(INTERNAL_API) ~= "table" then
  error("LidPanels missing internal API registry.")
end

local RequireApi = INTERNAL_API.LIDPANELS_RequireApi_Impl
if type(RequireApi) ~= "function" then
  error("LidPanels missing shared editor page helpers.")
end

local RootDB = RequireApi("LIDPANELS_RootDB", _G.LIDPANELS_RootDB)
local DB = RequireApi("LIDPANELS_DB", _G.LIDPANELS_DB)
local ApplyFrameSkin = RequireApi("LIDPANELS_ApplyFrameSkin", _G.LIDPANELS_ApplyFrameSkin)
local GetBackgroundPath = RequireApi("LIDPANELS_GetBackgroundPath", _G.LIDPANELS_GetBackgroundPath)
local RebuildAllPanels = RequireApi("LIDPANELS_RebuildAllPanels", _G.LIDPANELS_RebuildAllPanels)
local GetSelectedPanelName = RequireApi("LIDPANELS_GetSelectedPanelName", _G.LIDPANELS_GetSelectedPanelName)

local LogSilent = rawget(_G, "LIDPANELS_LogSilent")

local function RefreshPanelProperties(editor)
  if editor and editor.RefreshPanelPropertiesSection then
    editor:RefreshPanelPropertiesSection()
  elseif editor and editor.RefreshPanelsSection then
    editor:RefreshPanelsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
end

local function CreatePanelAssetPickers(editor, deps)
  if not editor then
    return {}
  end

  deps = deps or {}
  local bgPreview = deps.bgPreview
  local texturePickBtn = deps.texturePickBtn
  local iconPickBtn = deps.iconPickBtn
  local modelPickBtn = deps.modelPickBtn
  local textureEntries = deps.textureEntries or {}
  local modelEntries = deps.modelEntries or {}
  local clearPanelBackgroundState = deps.ClearPanelBackgroundState

  local function ClearBackgroundState(panel)
    if not panel then return end
    if type(clearPanelBackgroundState) == "function" then
      clearPanelBackgroundState(panel)
      return
    end
    panel.bg = "NONE"
    panel.bgType = nil
    panel.bgIconName = nil
    panel.bgTextureName = nil
    panel.bgModelId = nil
    panel.bgModelName = nil
    panel.bgModelSettings = nil
    panel.bgTexCoord = nil
    panel.bgChoiceKey = nil
  end

  local function HideOtherPickers(activeKey)
    local keys = { "iconPicker", "texturePicker", "modelPicker" }
    for _, key in ipairs(keys) do
      if key ~= activeKey and editor[key] then
        editor[key]:Hide()
      end
    end
  end

  local function BuildCustomBackgroundOptions()
    local options = { { text = "None", value = "NONE" } }
    local customEntries = {}
    local root = RootDB and RootDB()
    if root and root.backgroundCategories then
      for _, cat in ipairs(root.backgroundCategories) do
        if cat.items then
          for _, name in ipairs(cat.items) do
            if type(name) == "string" and name ~= "" then
              local path = (GetBackgroundPath and GetBackgroundPath(name))
                or ("Interface\\AddOns\\LidPanels_UserMedia\\Your_Backgrounds\\" .. name .. ".tga")
              customEntries[#customEntries + 1] = { text = name, value = path }
            end
          end
        end
      end
    end
    table.sort(customEntries, function(a, b)
      local aText = string.lower(tostring((a and a.text) or ""))
      local bText = string.lower(tostring((b and b.text) or ""))
      if aText == bText then
        return string.lower(tostring((a and a.value) or "")) < string.lower(tostring((b and b.value) or ""))
      end
      return aText < bText
    end)
    for _, entry in ipairs(customEntries) do
      options[#options + 1] = entry
    end
    return options
  end

  local function BuildIconList(force)
    if editor and editor.iconListCache and not force then
      return editor.iconListCache
    end
    local iconNames = _G.LIDPANELS_ICON_FILE_NAMES
    local api = _G.LIDPANELS_API
    if type(iconNames) ~= "table" and type(api) == "table" then
      iconNames = api.LIDPANELS_ICON_FILE_NAMES
    end
    local list, seen = {}, {}

    local function labelFromTexture(texture)
      local s = tostring(texture or "")
      local name = s:match("([^\\]+)$") or s
      local num = tonumber(name)
      if num and iconNames and iconNames[num] then
        return iconNames[num]
      end
      return name
    end

    local function add(label, texture, cat)
      if not label or label == "" or not texture then return end
      local key = tostring(texture) .. ":" .. tostring(label)
      if seen[key] then return end
      seen[key] = true
      list[#list + 1] = {
        label = label,
        texture = texture,
        search = string.lower(label),
        cat = cat or "ALL",
      }
    end

    if type(iconNames) == "table" then
      for id, name in pairs(iconNames) do
        add(tostring(name), tonumber(id) or id, "ALL")
      end
    elseif LogSilent and not editor.__iconNamesWarned then
      editor.__iconNamesWarned = true
      LogSilent("LIDPANELS icon name table missing; icon picker limited to macro icons.")
    end

    local macroIconsFn = (C_Macro and C_Macro.GetMacroIcons) or GetMacroIcons
    local macroItemIconsFn = (C_Macro and C_Macro.GetMacroItemIcons) or GetMacroItemIcons
    if macroIconsFn then
      for _, tex in ipairs(macroIconsFn()) do
        add(labelFromTexture(tex), tex, "SPELL")
      end
    end
    if macroItemIconsFn then
      for _, tex in ipairs(macroItemIconsFn()) do
        add(labelFromTexture(tex), tex, "ITEM")
      end
    end

    table.sort(list, function(a, b) return a.label < b.label end)
    editor.iconListCache = list
    return list
  end

  local function ShowIconPicker(onSelect)
    HideOtherPickers("iconPicker")
    if not editor.iconPicker then
      local picker = CreateFrame("Frame", "LIDPANELS_IconPicker", UIParent, "BackdropTemplate")
      picker:SetFrameStrata("DIALOG")
      picker:SetSize(760, 480)
      picker:SetPoint("CENTER")
      picker:SetClampedToScreen(true)
      picker:SetMovable(true)
      picker:EnableMouse(true)
      picker:RegisterForDrag("LeftButton")
      picker:SetScript("OnDragStart", function(self) self:StartMoving() end)
      picker:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
      ApplyFrameSkin(picker)

      local title = picker:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
      title:SetPoint("TOPLEFT", 12, -10)
      title:SetText("Select Icon")

      local close = CreateFrame("Button", nil, picker, "UIPanelCloseButton")
      close:SetPoint("TOPRIGHT", -2, -2)
      close:SetScript("OnClick", function() picker:Hide() end)

      local filterFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      filterFrame:SetPoint("TOPLEFT", 12, -64)
      filterFrame:SetPoint("BOTTOMLEFT", 12, 42)
      filterFrame:SetWidth(110)
      ApplyFrameSkin(filterFrame)
      picker.filterFrame = filterFrame

      local catTitle = filterFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      catTitle:SetPoint("TOPLEFT", 10, -6)
      catTitle:SetText("Categories")
      picker.catTitle = catTitle

      local subFilterFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      subFilterFrame:SetPoint("TOPLEFT", filterFrame, "TOPRIGHT", 8, 0)
      subFilterFrame:SetPoint("BOTTOMLEFT", filterFrame, "BOTTOMRIGHT", 8, 0)
      subFilterFrame:SetWidth(160)
      ApplyFrameSkin(subFilterFrame)
      picker.subFilterFrame = subFilterFrame

      local catScroll = CreateFrame("ScrollFrame", nil, filterFrame, "UIPanelScrollFrameTemplate")
      catScroll:SetPoint("TOPLEFT", 6, -24)
      catScroll:SetPoint("BOTTOMRIGHT", -22, 6)
      filterFrame.catScroll = catScroll

      local catContent = CreateFrame("Frame", nil, catScroll)
      catContent:SetPoint("TOPLEFT", 0, 0)
      catContent:SetSize(1, 1)
      catScroll:SetScrollChild(catContent)
      filterFrame.catContent = catContent

      local subScroll = CreateFrame("ScrollFrame", nil, subFilterFrame, "UIPanelScrollFrameTemplate")
      subScroll:SetPoint("TOPLEFT", 6, -6)
      subScroll:SetPoint("BOTTOMRIGHT", -22, 6)
      subFilterFrame.subScroll = subScroll

      local subContent = CreateFrame("Frame", nil, subScroll)
      subContent:SetPoint("TOPLEFT", 0, 0)
      subContent:SetSize(1, 1)
      subScroll:SetScrollChild(subContent)
      subFilterFrame.subContent = subContent

      local categoryButtons = {}
      picker.categoryButtons = categoryButtons
      editor.iconCategoryButtons = categoryButtons

      local subButtons = {}
      picker.subButtons = subButtons
      editor.iconSubButtons = subButtons

      local categories = {
        { key = "ALL", label = "All" },
        { key = "SPELLS", label = "Spells" },
        { key = "ITEMS", label = "Items" },
        { key = "CLASSES", label = "Classes" },
        { key = "PROF", label = "Profession" },
        { key = "EXP", label = "Expansion" },
        { key = "MISC", label = "Misc" },
        { key = "TEX", label = "Textures" },
      }

      local function EnsureCatButton(idx)
        local b = categoryButtons[idx]
        if not b then
          b = CreateFrame("Button", nil, catContent, "UIPanelButtonTemplate")
          b:SetSize(90, 22)
          categoryButtons[idx] = b
        end
        return b
      end

      local y = -4
      for i, cat in ipairs(categories) do
        local b = EnsureCatButton(i)
        b:ClearAllPoints()
        b:SetPoint("TOPLEFT", 0, y)
        b:SetText(cat.label)
        b.__key = cat.key
        y = y - 26
      end
      catContent:SetSize(90, math.max(1, -y + 6))

      local subTitle = subFilterFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      subTitle:SetPoint("TOPLEFT", 10, -6)
      subTitle:SetText("Preset Filters")
      picker.subTitle = subTitle

      local function EnsureSubButton(idx)
        local b = subButtons[idx]
        if not b then
          b = CreateFrame("Button", nil, subContent, "UIPanelButtonTemplate")
          b:SetSize(120, 22)
          subButtons[idx] = b
        end
        return b
      end

      if editor.ApplyTheme then editor:ApplyTheme() end

      local iconFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      iconFrame:SetPoint("TOPLEFT", subFilterFrame, "TOPRIGHT", 8, 0)
      iconFrame:SetPoint("BOTTOMRIGHT", -12, 42)
      ApplyFrameSkin(iconFrame)
      picker.iconFrame = iconFrame

      local scroll = CreateFrame("ScrollFrame", nil, iconFrame, "UIPanelScrollFrameTemplate")
      scroll:SetPoint("TOPLEFT", 6, -34)
      scroll:SetPoint("BOTTOMRIGHT", -28, 32)
      scroll:EnableMouseWheel(true)
      picker.scroll = scroll

      local content = CreateFrame("Frame", nil, scroll)
      content:SetPoint("TOPLEFT", 0, 0)
      content:SetSize(1, 1)
      scroll:SetScrollChild(content)
      picker.content = content

      picker.buttons = {}
      picker.iconList = BuildIconList()
      picker.filter = "ALL"
      picker.filterCache = {}
      picker.pageSize = 130
      picker.page = 1
      picker.search = ""

      local searchBox = CreateFrame("EditBox", nil, picker, "InputBoxTemplate")
      searchBox:SetSize(260, 20)
      searchBox:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 8, -8)
      searchBox:SetAutoFocus(false)
      searchBox:SetText("")
      picker.searchBox = searchBox

      local searchLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      searchLabel:SetPoint("RIGHT", searchBox, "LEFT", -6, 0)
      searchLabel:SetText("Search:")

      local prevBtn = CreateFrame("Button", nil, picker, "UIPanelButtonTemplate")
      prevBtn:SetSize(28, 20)
      prevBtn:SetPoint("BOTTOMLEFT", iconFrame, "BOTTOMLEFT", 6, 6)
      prevBtn:SetText("<")
      picker.prevBtn = prevBtn
      editor.iconPrevBtn = prevBtn

      local nextBtn = CreateFrame("Button", nil, picker, "UIPanelButtonTemplate")
      nextBtn:SetSize(28, 20)
      nextBtn:SetPoint("LEFT", prevBtn, "RIGHT", 6, 0)
      nextBtn:SetText(">")
      picker.nextBtn = nextBtn
      editor.iconNextBtn = nextBtn

      local pageLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      pageLabel:SetPoint("LEFT", nextBtn, "RIGHT", 10, 0)
      pageLabel:SetText("Page 1 / 1")
      picker.pageLabel = pageLabel

      local pageEdit = CreateFrame("EditBox", nil, picker, "InputBoxTemplate")
      pageEdit:SetSize(40, 20)
      pageEdit:SetPoint("LEFT", pageLabel, "RIGHT", 8, 0)
      pageEdit:SetAutoFocus(false)
      pageEdit:SetNumeric(true)
      pageEdit:SetText("1")
      picker.pageEdit = pageEdit

      local function EnsureButton(idx, size)
        local b = picker.buttons[idx]
        if not b then
          b = CreateFrame("Button", nil, content)
          b:SetSize(size, size)
          local t = b:CreateTexture(nil, "ARTWORK")
          t:SetAllPoints(true)
          b.tex = t
          b:SetScript("OnEnter", function(self)
            if self.__label then
              GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
              GameTooltip:SetText(self.__label)
              GameTooltip:Show()
            end
          end)
          b:SetScript("OnLeave", function() GameTooltip:Hide() end)
          picker.buttons[idx] = b
        end
        return b
      end

      local SUBFILTERS = {
        CLASSES = {
          { label = "Warrior", keywords = { "warrior" } },
          { label = "Hunter", keywords = { "hunter" } },
          { label = "Priest", keywords = { "priest" } },
          { label = "Mage", keywords = { "mage" } },
          { label = "Monk", keywords = { "monk" } },
          { label = "Demonhunter", keywords = { "demonhunter", "demon_hunter" } },
          { label = "Evoker", keywords = { "evoker" } },
          { label = "Paladin", keywords = { "paladin" } },
          { label = "Rogue", keywords = { "rogue" } },
          { label = "Shaman", keywords = { "shaman" } },
          { label = "Warlock", keywords = { "warlock" } },
          { label = "Druid", keywords = { "druid" } },
          { label = "Deathknight", keywords = { "deathknight", "death_knight" } },
        },
        PROF = {
          { label = "Blacksmith", keywords = { "blacksmith", "blacksmithing" } },
          { label = "Inscription", keywords = { "inscription", "inscribe" } },
          { label = "Enchantment", keywords = { "enchant", "enchanting" } },
          { label = "Jewelry", keywords = { "jewel", "jewelcraft" } },
          { label = "Leatherworking", keywords = { "leatherwork", "leatherworking" } },
          { label = "Skinning", keywords = { "skinning" } },
          { label = "Fishing", keywords = { "fishing" } },
          { label = "Tailoring", keywords = { "tailor", "tailoring" } },
          { label = "Cooking", keywords = { "cooking" } },
          { label = "Engineering", keywords = { "engineer", "engineering" } },
          { label = "Mining", keywords = { "mining" } },
        },
        EXP = {
          { label = "Ardenweald", keywords = { "ardenweald" } },
          { label = "Revendreth", keywords = { "revendreth" } },
          { label = "Maldraxxus", keywords = { "maldraxxus" } },
          { label = "Kyrian", keywords = { "kyrian" } },
        },
        MISC = {
          { label = "PVP", keywords = { "achievement_pvp" } },
          { label = "Challengemode", keywords = { "achievement_challengemode" } },
          { label = "Zone", keywords = { "achievement_zone" } },
          { label = "Mounts", keywords = { "mount" } },
          { label = "Pets", keywords = { "pet" } },
        },
        TEX = {
          { label = "Walls", keywords = { "wall" } },
          { label = "Stairs", keywords = { "stair" } },
          { label = "Iron", keywords = { "iron" } },
          { label = "Leather", keywords = { "leather" } },
          { label = "Garrison", keywords = { "garrison" } },
        },
      }

      local SPELL_KEYWORDS = { "ability_", "spell_", "spell", "magic" }
      local ITEM_KEYWORDS = { "inv_", "item_", "artifact", "trade_" }

      local function FilterByKeywords(list, keywords)
        if not keywords then return {} end
        local filtered = {}
        for _, icon in ipairs(list) do
          local s = icon.search
          if s then
            for _, k in ipairs(keywords) do
              if s:find(k, 1, true) then
                filtered[#filtered + 1] = icon
                break
              end
            end
          end
        end
        return filtered
      end

      local RefreshResults

      local function RenderSubFilters(catKey)
        local list = SUBFILTERS[catKey]
        if not list then
          picker.subFilterKeywords = nil
          picker.subFilterLabel = nil
          subTitle:SetText("Filters")
          for i = 1, #subButtons do
            subButtons[i]:Hide()
          end
          return
        end

        subTitle:SetText("Filters")
        local yPos = -34
        for i, entry in ipairs(list) do
          local b = EnsureSubButton(i)
          b:ClearAllPoints()
          b:SetPoint("TOPLEFT", 10, yPos)
          b:SetText(entry.label)
          b.__keywords = entry.keywords
          b:SetScript("OnClick", function()
            picker.subFilterKeywords = entry.keywords
            picker.subFilterLabel = entry.label
            if RefreshResults then RefreshResults(true) end
          end)
          b:Show()
          yPos = yPos - 26
        end
        for i = #list + 1, #subButtons do
          subButtons[i]:Hide()
        end
        picker.subFilterKeywords = list[1] and list[1].keywords or nil
        picker.subFilterLabel = list[1] and list[1].label or nil
        subContent:SetSize(120, math.max(1, -yPos + 6))
        if editor.ApplyTheme then editor:ApplyTheme() end
      end

      local function GetFilteredList(cat)
        local base = picker.iconList or {}
        if not cat or cat == "ALL" then
          return base
        end
        if cat == "SPELLS" then
          return FilterByKeywords(base, SPELL_KEYWORDS)
        end
        if cat == "ITEMS" then
          return FilterByKeywords(base, ITEM_KEYWORDS)
        end
        if picker.subFilterKeywords then
          return FilterByKeywords(base, picker.subFilterKeywords)
        end
        return {}
      end

      local function BuildSearchList(list, term)
        if not term or term == "" then return list end
        local filtered = {}
        for _, icon in ipairs(list) do
          if icon.search and icon.search:find(term, 1, true) then
            filtered[#filtered + 1] = icon
          end
        end
        return filtered
      end

      local function GetFilterCacheKey()
        local subKey = ""
        if picker.filter ~= "ALL" and picker.filter ~= "SPELLS" and picker.filter ~= "ITEMS" then
          subKey = tostring(picker.subFilterLabel or "")
        end
        return table.concat({
          tostring(picker.filter or "ALL"),
          subKey,
          tostring(picker.search or ""),
        }, "\31")
      end

      local function LayoutPage(results)
        local availW = (scroll:GetWidth() or 1) - 12
        local availH = (scroll:GetHeight() or 1) - 12
        if availW < 50 or availH < 50 then
          if not picker.__pendingLayout then
            picker.__pendingLayout = true
            C_Timer.After(0.05, function()
              picker.__pendingLayout = false
              if picker:IsShown() and picker.RefreshResults then
                picker:RefreshResults(false)
              end
            end)
          end
          return
        end

        local rows, cols = 13, 10
        local pad = 4
        local size = math.max(10, math.floor((availW - pad * (cols - 1)) / cols))
        local rowH = size + pad
        local startIndex = (picker.page - 1) * picker.pageSize + 1
        local x0, y0 = 6, -6

        for i = 1, picker.pageSize do
          local icon = results[startIndex + i - 1]
          local b = EnsureButton(i, size)
          if icon then
            local col = (i - 1) % cols
            local row = math.floor((i - 1) / cols)
            b:ClearAllPoints()
            b:SetPoint("TOPLEFT", x0 + col * (size + pad), y0 - row * rowH)
            b.tex:SetTexture(icon.texture)
            b.__label = icon.label
            b.__texture = icon.texture
            b:SetScript("OnClick", function()
              if picker.onSelect then picker.onSelect(icon) end
              picker:Hide()
            end)
            b:Show()
          else
            b:Hide()
          end
        end

        local neededH = rows * rowH + 6
        content:SetSize(availW, neededH)
        if neededH <= availH then
          scroll:SetVerticalScroll(0)
        end
      end

      RefreshResults = function(resetPage)
        if resetPage then picker.page = 1 end
        picker.filterCache = picker.filterCache or {}
        local cacheKey = GetFilterCacheKey()
        local list = picker.filterCache[cacheKey]
        if not list then
          list = GetFilteredList(picker.filter)
          list = BuildSearchList(list, picker.search)
          picker.filterCache[cacheKey] = list
        end
        picker.filteredResults = list
        local total = #list
        local totalPages = math.max(1, math.ceil(total / picker.pageSize))
        if picker.page > totalPages then picker.page = totalPages end
        if picker.page < 1 then picker.page = 1 end
        picker.pageLabel:SetText("Page " .. picker.page .. " / " .. totalPages)
        if picker.pageEdit and picker.pageEdit.HasFocus and not picker.pageEdit:HasFocus() then
          picker.pageEdit:SetText(tostring(picker.page))
        end
        LayoutPage(list)
      end

      local function SetCategory(cat)
        picker.filter = cat or "ALL"
        RenderSubFilters(picker.filter)
        RefreshResults(true)
      end

      for _, b in ipairs(categoryButtons) do
        b:SetScript("OnClick", function() SetCategory(b.__key) end)
      end
      SetCategory("ALL")

      searchBox:SetScript("OnTextChanged", function(self)
        picker.search = string.lower(self:GetText() or "")
        RefreshResults(true)
      end)
      searchBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)

      prevBtn:SetScript("OnClick", function()
        picker.page = math.max(1, picker.page - 1)
        RefreshResults(false)
      end)
      nextBtn:SetScript("OnClick", function()
        picker.page = picker.page + 1
        RefreshResults(false)
      end)
      pageEdit:SetScript("OnEnterPressed", function(self)
        local v = tonumber(self:GetText() or "")
        if v then picker.page = v end
        RefreshResults(false)
        self:ClearFocus()
      end)
      pageEdit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)

      scroll:EnableMouseWheel(true)
      if scroll.ScrollBar then scroll.ScrollBar:Show() end
      scroll:HookScript("OnSizeChanged", function() RefreshResults(false) end)

      picker.RefreshResults = RefreshResults
      picker:HookScript("OnShow", function() RefreshResults(true) end)
      RefreshResults(true)
      editor.iconPicker = picker
      if editor.ApplyTheme then editor:ApplyTheme() end
    end

    editor.iconPicker.onSelect = onSelect
    editor.iconPicker.iconList = BuildIconList()
    editor.iconPicker.filterCache = {}
    editor.iconPicker:Show()
    if editor.iconPicker.RefreshResults then
      editor.iconPicker:RefreshResults()
    end
  end

  local function CopyTexCoord(texCoord)
    if type(texCoord) ~= "table" then return nil end
    local u1 = tonumber(texCoord[1])
    local u2 = tonumber(texCoord[2])
    local v1 = tonumber(texCoord[3])
    local v2 = tonumber(texCoord[4])
    if not (u1 and u2 and v1 and v2) then return nil end
    return { u1, u2, v1, v2 }
  end

  local function SameTexCoord(a, b)
    if a == nil and b == nil then return true end
    if type(a) ~= "table" or type(b) ~= "table" then return false end
    for i = 1, 4 do
      local av = tonumber(a[i]) or 0
      local bv = tonumber(b[i]) or 0
      if math.abs(av - bv) > 0.0001 then
        return false
      end
    end
    return true
  end

  local function ClampPreviewNumber(value, defaultValue, minValue, maxValue)
    local n = tonumber(value)
    if n == nil then return defaultValue end
    if minValue ~= nil and n < minValue then n = minValue end
    if maxValue ~= nil and n > maxValue then n = maxValue end
    return n
  end

  local function SafeSetPreviewTexture(texture, path, texCoord)
    if not texture or not path or path == "NONE" then
      return false
    end
    local ok = pcall(texture.SetTexture, texture, path)
    if not ok then
      texture:SetTexture(nil)
      return false
    end
    if type(texCoord) == "table" then
      texture:SetTexCoord(
        ClampPreviewNumber(texCoord[1], 0, 0, 1),
        ClampPreviewNumber(texCoord[2], 1, 0, 1),
        ClampPreviewNumber(texCoord[3], 0, 0, 1),
        ClampPreviewNumber(texCoord[4], 1, 0, 1)
      )
    else
      texture:SetTexCoord(0, 1, 0, 1)
    end
    return true
  end

  local function FindTalentOptionForPanel(panel, options)
    if type(panel) ~= "table" or type(options) ~= "table" then return nil end
    if panel.bgChoiceKey then
      for _, opt in ipairs(options) do
        if opt and opt.value == panel.bgChoiceKey then
          return opt
        end
      end
    end
    for _, opt in ipairs(options) do
      if opt and opt.bg == panel.bg and SameTexCoord(opt.texCoord, panel.bgTexCoord) then
        return opt
      end
    end
    return nil
  end

  local function UpdateBgPreview(path, texCoord)
    if not bgPreview or not bgPreview.texture then
      return
    end
    if not path or path == "NONE" then
      bgPreview:Hide()
      return
    end
    if SafeSetPreviewTexture(bgPreview.texture, path, texCoord) then
      bgPreview:Show()
    else
      bgPreview:Hide()
    end
  end

  local function SetPanelBackground(value, kind, label, modelSettings, texCoord, choiceKey)
    if not GetSelectedPanelName() then return end
    local p = DB().panels[GetSelectedPanelName()]
    if not p then return end
    p.color = p.color or { 0.05, 0.05, 0.07, 0.8 }
    if kind == "TALENT" or kind == "CUSTOM" then
      p.designMode = "BG"
    elseif kind == "TEX" or kind == "ICON" or kind == "MODEL" then
      p.designMode = kind
    end

    if value == "NONE" then
      ClearBackgroundState(p)
    else
      p.bg = value
      p.bgType = kind
      if kind == "ICON" then
        p.bgIconName = label
        p.bgTextureName = nil
        p.bgModelId = nil
        p.bgModelName = nil
        p.bgModelSettings = nil
        p.bgTexCoord = nil
        p.bgChoiceKey = nil
      elseif kind == "TEX" then
        p.bgTextureName = label
        p.bgIconName = nil
        p.bgModelId = nil
        p.bgModelName = nil
        p.bgModelSettings = nil
        p.bgTexCoord = nil
        p.bgChoiceKey = nil
      elseif kind == "MODEL" then
        p.bgModelId = value
        p.bgModelName = label
        p.bgIconName = nil
        p.bgTextureName = nil
        local s = modelSettings or p.bgModelSettings or {}
        p.bgModelSettings = {
          zoom = s.zoom or 1,
          facing = s.facing or 0,
          x = s.x or 0,
          y = s.y or 0,
        }
        p.bgTexCoord = nil
        p.bgChoiceKey = nil
      elseif kind == "TALENT" then
        p.bgIconName = nil
        p.bgTextureName = nil
        p.bgModelId = nil
        p.bgModelName = nil
        p.bgModelSettings = nil
        p.bgTexCoord = CopyTexCoord(texCoord)
        p.bgChoiceKey = choiceKey
      else
        p.bgIconName = nil
        p.bgTextureName = nil
        p.bgModelId = nil
        p.bgModelName = nil
        p.bgModelSettings = nil
        p.bgTexCoord = nil
        p.bgChoiceKey = nil
      end
      p.gradient = nil
    end

    if p.designMode == "MODEL" then
      if bgPreview then bgPreview:Hide() end
    else
      UpdateBgPreview(p.bg, p.bgTexCoord)
    end
    RebuildAllPanels()
    RefreshPanelProperties(editor)
  end

  local function ShowTexturePicker(onSelect)
    HideOtherPickers("texturePicker")
    if not editor.texturePicker then
      local picker = CreateFrame("Frame", "LIDPANELS_TexturePicker", UIParent, "BackdropTemplate")
      picker:SetFrameStrata("DIALOG")
      picker:SetSize(760, 480)
      picker:SetPoint("CENTER")
      picker:SetClampedToScreen(true)
      picker:SetMovable(true)
      picker:EnableMouse(true)
      picker:RegisterForDrag("LeftButton")
      picker:SetScript("OnDragStart", function(self) self:StartMoving() end)
      picker:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
      ApplyFrameSkin(picker)

      local title = picker:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
      title:SetPoint("TOPLEFT", 12, -10)
      title:SetText("Select Texture")

      local close = CreateFrame("Button", nil, picker, "UIPanelCloseButton")
      close:SetPoint("TOPRIGHT", -2, -2)
      close:SetScript("OnClick", function() picker:Hide() end)

      local filterFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      filterFrame:SetPoint("TOPLEFT", 12, -64)
      filterFrame:SetPoint("BOTTOMLEFT", 12, 42)
      filterFrame:SetWidth(110)
      ApplyFrameSkin(filterFrame)
      picker.filterFrame = filterFrame

      local catTitle = filterFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      catTitle:SetPoint("TOPLEFT", 10, -6)
      catTitle:SetText("Categories")

      local catScroll = CreateFrame("ScrollFrame", nil, filterFrame, "UIPanelScrollFrameTemplate")
      catScroll:SetPoint("TOPLEFT", 6, -24)
      catScroll:SetPoint("BOTTOMRIGHT", -22, 6)
      local catContent = CreateFrame("Frame", nil, catScroll)
      catContent:SetPoint("TOPLEFT", 0, 0)
      catContent:SetSize(1, 1)
      catScroll:SetScrollChild(catContent)

      local subFilterFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      subFilterFrame:SetPoint("TOPLEFT", filterFrame, "TOPRIGHT", 8, 0)
      subFilterFrame:SetPoint("BOTTOMLEFT", filterFrame, "BOTTOMRIGHT", 8, 0)
      subFilterFrame:SetWidth(160)
      ApplyFrameSkin(subFilterFrame)

      local subTitle = subFilterFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      subTitle:SetPoint("TOPLEFT", 10, -6)
      subTitle:SetText("Preset Filters")

      local subScroll = CreateFrame("ScrollFrame", nil, subFilterFrame, "UIPanelScrollFrameTemplate")
      subScroll:SetPoint("TOPLEFT", 6, -24)
      subScroll:SetPoint("BOTTOMRIGHT", -22, 6)
      local subContent = CreateFrame("Frame", nil, subScroll)
      subContent:SetPoint("TOPLEFT", 0, 0)
      subContent:SetSize(1, 1)
      subScroll:SetScrollChild(subContent)

      local iconFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      iconFrame:SetPoint("TOPLEFT", subFilterFrame, "TOPRIGHT", 8, 0)
      iconFrame:SetPoint("BOTTOMRIGHT", -12, 42)
      ApplyFrameSkin(iconFrame)

      local scroll = CreateFrame("ScrollFrame", nil, iconFrame, "UIPanelScrollFrameTemplate")
      scroll:SetPoint("TOPLEFT", 6, -34)
      scroll:SetPoint("BOTTOMRIGHT", -28, 32)
      scroll:EnableMouseWheel(true)

      local content = CreateFrame("Frame", nil, scroll)
      content:SetPoint("TOPLEFT", 0, 0)
      content:SetSize(1, 1)
      scroll:SetScrollChild(content)

      local searchBox = CreateFrame("EditBox", nil, picker, "InputBoxTemplate")
      searchBox:SetSize(260, 20)
      searchBox:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 8, -8)
      searchBox:SetAutoFocus(false)
      searchBox:SetText("")

      local searchLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      searchLabel:SetPoint("RIGHT", searchBox, "LEFT", -6, 0)
      searchLabel:SetText("Search:")

      local prevBtn = CreateFrame("Button", nil, picker, "UIPanelButtonTemplate")
      prevBtn:SetSize(28, 20)
      prevBtn:SetPoint("BOTTOMLEFT", iconFrame, "BOTTOMLEFT", 6, 6)
      prevBtn:SetText("<")

      local nextBtn = CreateFrame("Button", nil, picker, "UIPanelButtonTemplate")
      nextBtn:SetSize(28, 20)
      nextBtn:SetPoint("LEFT", prevBtn, "RIGHT", 6, 0)
      nextBtn:SetText(">")

      local pageLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      pageLabel:SetPoint("LEFT", nextBtn, "RIGHT", 10, 0)
      pageLabel:SetText("Page 1 / 1")

      local pageEdit = CreateFrame("EditBox", nil, picker, "InputBoxTemplate")
      pageEdit:SetSize(40, 20)
      pageEdit:SetPoint("LEFT", pageLabel, "RIGHT", 8, 0)
      pageEdit:SetAutoFocus(false)
      pageEdit:SetNumeric(true)
      pageEdit:SetText("1")

      local buttons = {}
      local entries = textureEntries

      local catLabels = {}
      local catSeen = {}
      for _, entry in ipairs(entries) do
        local cat = entry.cat or "Other"
        if not catSeen[cat] then
          catSeen[cat] = true
          catLabels[#catLabels + 1] = cat
        end
      end
      table.sort(catLabels, function(a, b)
        return string.lower(a) < string.lower(b)
      end)
      local categories = { { key = "ALL", label = "All" } }
      local subfilters = { ALL = { { label = "All" } } }
      for _, label in ipairs(catLabels) do
        categories[#categories + 1] = { key = label, label = label }
        subfilters[label] = { { label = "All" } }
      end

      local function EnsureButton(idx, size)
        local b = buttons[idx]
        if not b then
          b = CreateFrame("Button", nil, content)
          b:SetSize(size, size)
          local t = b:CreateTexture(nil, "ARTWORK")
          t:SetAllPoints(true)
          b.tex = t
          b:SetScript("OnEnter", function(self)
            if self.__label then
              GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
              GameTooltip:SetText(self.__label)
              GameTooltip:Show()
            end
          end)
          b:SetScript("OnLeave", function() GameTooltip:Hide() end)
          buttons[idx] = b
        end
        return b
      end

      local catButtons = {}
      local subButtons = {}
      editor.textureCategoryButtons = catButtons
      editor.textureSubButtons = subButtons

      local function EnsureCatButton(idx)
        local b = catButtons[idx]
        if not b then
          b = CreateFrame("Button", nil, catContent, "UIPanelButtonTemplate")
          b:SetSize(90, 22)
          catButtons[idx] = b
        end
        return b
      end

      local y = -4
      for i, cat in ipairs(categories) do
        local b = EnsureCatButton(i)
        b:ClearAllPoints()
        b:SetPoint("TOPLEFT", 0, y)
        b:SetText(cat.label)
        b.__key = cat.key
        y = y - 26
      end
      catContent:SetSize(90, math.max(1, -y + 6))

      local function EnsureSubButton(idx)
        local b = subButtons[idx]
        if not b then
          b = CreateFrame("Button", nil, subContent, "UIPanelButtonTemplate")
          b:SetSize(120, 22)
          subButtons[idx] = b
        end
        return b
      end

      local pageSize = 130
      local page = 1
      local catKey = "ALL"
      local subKey = "All"
      local search = ""
      local cachedFilterKey = nil
      local cachedFilterList = nil

      local function FilterEntries()
        local filterKey = table.concat({
          tostring(catKey or "ALL"),
          tostring(subKey or "All"),
          tostring(search or ""),
        }, "\31")
        if cachedFilterKey == filterKey and cachedFilterList then
          return cachedFilterList
        end
        local list = {}
        for _, entry in ipairs(entries) do
          if catKey == "ALL" or entry.cat == catKey then
            if catKey == "ALL" or entry.filter == subKey then
              if search == "" or entry.search:find(search, 1, true) then
                list[#list + 1] = entry
              end
            end
          end
        end
        cachedFilterKey = filterKey
        cachedFilterList = list
        return list
      end

      local function LayoutPage(list)
        local availW = (scroll:GetWidth() or 1) - 12
        local availH = (scroll:GetHeight() or 1) - 12
        local rows, cols = 13, 10
        local pad = 4
        local size = math.max(10, math.floor((availW - pad * (cols - 1)) / cols))
        local rowH = size + pad
        local neededH = rows * rowH + 6
        local startIndex = (page - 1) * pageSize + 1
        local x0, y0 = 6, -6
        for i = 1, pageSize do
          local item = list[startIndex + i - 1]
          local b = EnsureButton(i, size)
          if item then
            local col = (i - 1) % cols
            local row = math.floor((i - 1) / cols)
            b:ClearAllPoints()
            b:SetPoint("TOPLEFT", x0 + col * (size + pad), y0 - row * rowH)
            b.tex:SetTexture(item.path)
            b.__label = item.label
            b:SetScript("OnClick", function()
              if onSelect then onSelect(item) end
              picker:Hide()
            end)
            b:Show()
          else
            b:Hide()
          end
        end
        content:SetSize(availW, neededH)
        scroll:SetVerticalScroll(0)
      end

      local function Refresh()
        local list = FilterEntries()
        local total = #list
        local pages = math.max(1, math.ceil(total / pageSize))
        if page > pages then page = pages end
        if page < 1 then page = 1 end
        pageLabel:SetText("Page " .. page .. " / " .. pages)
        if pageEdit and pageEdit.HasFocus and not pageEdit:HasFocus() then
          pageEdit:SetText(tostring(page))
        end
        LayoutPage(list)
      end

      local function RenderSubFilters()
        local list = subfilters[catKey] or { { label = "All" } }
        local yPos = -4
        for i, entry in ipairs(list) do
          local b = EnsureSubButton(i)
          b:ClearAllPoints()
          b:SetPoint("TOPLEFT", 0, yPos)
          b:SetText(entry.label)
          b:SetScript("OnClick", function()
            subKey = entry.label
            Refresh()
          end)
          b:Show()
          yPos = yPos - 26
        end
        for i = #list + 1, #subButtons do
          subButtons[i]:Hide()
        end
        subKey = list[1] and list[1].label or "All"
        subContent:SetSize(120, math.max(1, -yPos + 6))
      end

      for _, b in ipairs(catButtons) do
        b:SetScript("OnClick", function()
          catKey = b.__key
          RenderSubFilters()
          Refresh()
        end)
      end
      RenderSubFilters()

      searchBox:SetScript("OnTextChanged", function(self)
        search = string.lower(self:GetText() or "")
        page = 1
        Refresh()
      end)
      searchBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)

      prevBtn:SetScript("OnClick", function()
        page = math.max(1, page - 1)
        Refresh()
      end)
      nextBtn:SetScript("OnClick", function()
        page = page + 1
        Refresh()
      end)
      pageEdit:SetScript("OnEnterPressed", function(self)
        local v = tonumber(self:GetText() or "")
        if v then page = v end
        Refresh()
        self:ClearFocus()
      end)
      pageEdit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)

      scroll:EnableMouseWheel(true)
      if scroll.ScrollBar then scroll.ScrollBar:Show() end
      scroll:HookScript("OnSizeChanged", function() Refresh() end)

      if editor.ApplyTheme then editor:ApplyTheme() end
      picker.Refresh = Refresh
      picker:HookScript("OnShow", function() Refresh() end)
      Refresh()
      editor.texturePicker = picker
    end

    editor.texturePicker.onSelect = onSelect
    editor.texturePicker:Show()
    if editor.texturePicker.Refresh then
      editor.texturePicker:Refresh()
    end
  end

  local function ShowModelPicker(onSelect)
    HideOtherPickers("modelPicker")
    if not editor.modelPicker then
      local picker = CreateFrame("Frame", "LIDPANELS_ModelPicker", UIParent, "BackdropTemplate")
      picker:SetFrameStrata("DIALOG")
      picker:SetSize(860, 560)
      picker:SetPoint("CENTER")
      picker:SetClampedToScreen(true)
      picker:SetMovable(true)
      picker:EnableMouse(true)
      picker:RegisterForDrag("LeftButton")
      picker:SetScript("OnDragStart", function(self) self:StartMoving() end)
      picker:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
      ApplyFrameSkin(picker)

      local title = picker:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
      title:SetPoint("TOPLEFT", 12, -10)
      title:SetText("Select Model")

      local close = CreateFrame("Button", nil, picker, "UIPanelCloseButton")
      close:SetPoint("TOPRIGHT", -2, -2)
      close:SetScript("OnClick", function() picker:Hide() end)

      local catFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      catFrame:SetPoint("TOPLEFT", 12, -64)
      catFrame:SetPoint("BOTTOMLEFT", 12, 42)
      catFrame:SetWidth(120)
      ApplyFrameSkin(catFrame)

      local catTitle = catFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      catTitle:SetPoint("TOPLEFT", 10, -6)
      catTitle:SetText("Categories")

      local catScroll = CreateFrame("ScrollFrame", nil, catFrame, "UIPanelScrollFrameTemplate")
      catScroll:SetPoint("TOPLEFT", 6, -24)
      catScroll:SetPoint("BOTTOMRIGHT", -22, 6)
      local catContent = CreateFrame("Frame", nil, catScroll)
      catContent:SetPoint("TOPLEFT", 0, 0)
      catContent:SetSize(1, 1)
      catScroll:SetScrollChild(catContent)

      local listFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      listFrame:SetPoint("TOPLEFT", catFrame, "TOPRIGHT", 12, 0)
      listFrame:SetPoint("BOTTOMLEFT", catFrame, "BOTTOMRIGHT", 12, 0)
      listFrame:SetWidth(240)
      ApplyFrameSkin(listFrame)

      local listTitle = listFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      listTitle:SetPoint("TOPLEFT", 10, -6)
      listTitle:SetText("Models")

      local listScroll = CreateFrame("ScrollFrame", nil, listFrame, "UIPanelScrollFrameTemplate")
      listScroll:SetPoint("TOPLEFT", 6, -24)
      listScroll:SetPoint("BOTTOMRIGHT", -22, 6)
      listScroll:EnableMouseWheel(true)
      listScroll:SetScript("OnMouseWheel", function(self, delta)
        local cur = self:GetVerticalScroll() or 0
        self:SetVerticalScroll(cur - (delta * 20))
      end)

      local listContent = CreateFrame("Frame", nil, listScroll)
      listContent:SetPoint("TOPLEFT", 0, 0)
      listContent:SetSize(1, 1)
      listScroll:SetScrollChild(listContent)

      local previewFrame = CreateFrame("Frame", nil, picker, "BackdropTemplate")
      previewFrame:SetPoint("TOPLEFT", listFrame, "TOPRIGHT", 12, 0)
      previewFrame:SetPoint("BOTTOMRIGHT", -12, 42)
      ApplyFrameSkin(previewFrame)

      local searchLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      searchLabel:SetPoint("TOPLEFT", listFrame, "TOPLEFT", 0, 26)
      searchLabel:SetText("Search:")

      local searchBox = CreateFrame("EditBox", nil, picker, "InputBoxTemplate")
      searchBox:SetSize(240, 20)
      searchBox:SetPoint("LEFT", searchLabel, "RIGHT", 6, 0)
      searchBox:SetAutoFocus(false)
      searchBox:SetText("")

      local searchBtn = CreateFrame("Button", nil, picker, "UIPanelButtonTemplate")
      searchBtn:SetSize(70, 20)
      searchBtn:SetPoint("LEFT", searchBox, "RIGHT", 6, 0)
      searchBtn:SetText("Search")

      local prevBtn = CreateFrame("Button", nil, picker, "UIPanelButtonTemplate")
      prevBtn:SetSize(28, 20)
      prevBtn:SetPoint("BOTTOMLEFT", listFrame, "BOTTOMLEFT", 0, -28)
      prevBtn:SetText("<")

      local nextBtn = CreateFrame("Button", nil, picker, "UIPanelButtonTemplate")
      nextBtn:SetSize(28, 20)
      nextBtn:SetPoint("LEFT", prevBtn, "RIGHT", 6, 0)
      nextBtn:SetText(">")

      local pageLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      pageLabel:SetPoint("LEFT", nextBtn, "RIGHT", 10, 0)
      pageLabel:SetText("Page 1 / 1")

      local pageEdit = CreateFrame("EditBox", nil, picker, "InputBoxTemplate")
      pageEdit:SetSize(40, 20)
      pageEdit:SetPoint("LEFT", pageLabel, "RIGHT", 8, 0)
      pageEdit:SetAutoFocus(false)
      pageEdit:SetNumeric(true)
      pageEdit:SetText("1")

      local previewTitle = previewFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      previewTitle:SetPoint("TOPLEFT", 10, -10)
      previewTitle:SetText("Preview")

      local previewId = previewFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      previewId:SetPoint("TOPLEFT", previewTitle, "BOTTOMLEFT", 0, -2)
      previewId:SetText("ID: -")

      local controls = CreateFrame("Frame", nil, previewFrame)
      controls:SetPoint("BOTTOMLEFT", 6, 6)
      controls:SetPoint("BOTTOMRIGHT", -6, 6)
      controls:SetHeight(130)

      local previewModel = CreateFrame("PlayerModel", nil, previewFrame)
      previewModel:SetPoint("TOPLEFT", 6, -30)
      previewModel:SetPoint("BOTTOMRIGHT", controls, "TOPRIGHT", 0, 6)
      previewModel:SetPortraitZoom(0)
      previewModel:SetModelScale(1)

      local zoomSlider = CreateFrame("Slider", "LIDPANELS_ModelZoomSlider", controls, "OptionsSliderTemplate")
      zoomSlider:SetPoint("TOPLEFT", 0, -6)
      zoomSlider:SetWidth(180)
      zoomSlider:SetMinMaxValues(0.25, 4.0)
      zoomSlider:SetValueStep(0.05)
      zoomSlider:SetObeyStepOnDrag(true)
      _G[zoomSlider:GetName() .. "Low"]:SetText("0.25")
      _G[zoomSlider:GetName() .. "High"]:SetText("4.0")
      _G[zoomSlider:GetName() .. "Text"]:SetText("Zoom")

      local rotSlider = CreateFrame("Slider", "LIDPANELS_ModelRotateSlider", controls, "OptionsSliderTemplate")
      rotSlider:SetPoint("TOPLEFT", 200, -6)
      rotSlider:SetWidth(180)
      rotSlider:SetMinMaxValues(0, 360)
      rotSlider:SetValueStep(5)
      rotSlider:SetObeyStepOnDrag(true)
      _G[rotSlider:GetName() .. "Low"]:SetText("0")
      _G[rotSlider:GetName() .. "High"]:SetText("360")
      _G[rotSlider:GetName() .. "Text"]:SetText("Rotate")

      local xSlider = CreateFrame("Slider", "LIDPANELS_ModelXOffsetSlider", controls, "OptionsSliderTemplate")
      xSlider:SetPoint("TOPLEFT", 0, -52)
      xSlider:SetWidth(180)
      xSlider:SetMinMaxValues(-2, 2)
      xSlider:SetValueStep(0.05)
      xSlider:SetObeyStepOnDrag(true)
      _G[xSlider:GetName() .. "Low"]:SetText("-2")
      _G[xSlider:GetName() .. "High"]:SetText("2")
      _G[xSlider:GetName() .. "Text"]:SetText("X Offset")

      local ySlider = CreateFrame("Slider", "LIDPANELS_ModelYOffsetSlider", controls, "OptionsSliderTemplate")
      ySlider:SetPoint("TOPLEFT", 200, -52)
      ySlider:SetWidth(180)
      ySlider:SetMinMaxValues(-2, 2)
      ySlider:SetValueStep(0.05)
      ySlider:SetObeyStepOnDrag(true)
      _G[ySlider:GetName() .. "Low"]:SetText("-2")
      _G[ySlider:GetName() .. "High"]:SetText("2")
      _G[ySlider:GetName() .. "Text"]:SetText("Y Offset")

      local resetBtn = CreateFrame("Button", nil, controls, "UIPanelButtonTemplate")
      resetBtn:SetSize(90, 20)
      resetBtn:SetPoint("TOPRIGHT", controls, "TOPRIGHT", -6, -92)
      resetBtn:SetText("Reset")

      local applyBtn = CreateFrame("Button", nil, controls, "UIPanelButtonTemplate")
      applyBtn:SetSize(90, 20)
      applyBtn:SetPoint("RIGHT", resetBtn, "LEFT", -8, 0)
      applyBtn:SetText("Apply")

      local entries = modelEntries
      local selectedItem = nil
      local previewState = { zoom = 1.0, facing = 0, x = 0, y = 0 }

      for _, entry in ipairs(entries) do
        if not entry.search then
          local label = entry.label or ("Model " .. tostring(entry.id))
          local path = entry.path or ""
          entry.search = string.lower(label .. " " .. tostring(entry.id or "") .. " " .. path)
        end
        local cat = entry.cat or "Other"
        if type(cat) == "string" and string.lower(cat) == "fog" then
          cat = "Fog"
        end
        entry.cat = cat
      end

      if GetSelectedPanelName() then
        local panel = DB().panels[GetSelectedPanelName()]
        if panel and panel.bgModelId then
          for _, entry in ipairs(entries) do
            if entry.id == panel.bgModelId then
              selectedItem = entry
              break
            end
          end
        end
        if panel and panel.bgModelSettings then
          previewState.zoom = panel.bgModelSettings.zoom or 1.0
          previewState.facing = panel.bgModelSettings.facing or 0
          previewState.x = panel.bgModelSettings.x or 0
          previewState.y = panel.bgModelSettings.y or 0
        end
      end

      local listButtons = {}
      editor.modelListButtons = listButtons

      local function ApplyPreviewTransform()
        previewModel:SetModelScale(ClampPreviewNumber(previewState.zoom, 1, 0.05, 10))
        previewModel:SetFacing(math.rad(ClampPreviewNumber(previewState.facing, 0, -360, 360)))
        previewModel:SetPosition(
          ClampPreviewNumber(previewState.x, 0, -10000, 10000),
          ClampPreviewNumber(previewState.y, 0, -10000, 10000),
          0
        )
      end

      local function SetPreview(item)
        local modelId = item and tonumber(item.id)
        if not item or not modelId or modelId <= 0 then
          previewModel:ClearModel()
          previewTitle:SetText("Preview")
          previewId:SetText("ID: -")
          return
        end
        previewTitle:SetText(item.label or ("Model " .. tostring(modelId)))
        previewId:SetText("ID: " .. tostring(modelId))
        local ok = pcall(function()
          previewModel:SetModel(modelId)
        end)
        if not ok then
          previewModel:ClearModel()
          return
        end
        ApplyPreviewTransform()
      end

      local function EnsureListButton(idx)
        local b = listButtons[idx]
        if not b then
          b = CreateFrame("Button", nil, listContent, "UIPanelButtonTemplate")
          b:SetSize(190, 22)
          b:SetNormalFontObject("GameFontHighlightSmall")
          local border = CreateFrame("Frame", nil, b, "BackdropTemplate")
          border:SetPoint("TOPLEFT", -1, 1)
          border:SetPoint("BOTTOMRIGHT", 1, -1)
          border:SetBackdrop({ edgeFile = "Interface/Buttons/WHITE8x8", edgeSize = 1 })
          border:SetBackdropBorderColor(1, 1, 1, 0.9)
          border:Hide()
          b.__selBorder = border
          b:SetScript("OnEnter", function(self)
            if self.__item then SetPreview(self.__item) end
          end)
          b:SetScript("OnClick", function(self)
            if self.__item then
              selectedItem = self.__item
              SetPreview(self.__item)
              for _, other in ipairs(listButtons) do
                if other.__selBorder then other.__selBorder:Hide() end
              end
              if self.__selBorder then self.__selBorder:Show() end
            end
          end)
          listButtons[idx] = b
          if editor.ApplyTheme then editor:ApplyTheme() end
        end
        return b
      end

      local catLabels = {}
      local catSeen = {}
      for _, entry in ipairs(entries) do
        local cat = entry.cat or "Other"
        if cat ~= "" and not catSeen[cat] then
          catSeen[cat] = true
          catLabels[#catLabels + 1] = cat
        end
      end
      table.sort(catLabels, function(a, b)
        return string.lower(a) < string.lower(b)
      end)
      local categories = { { key = "ALL", label = "All" } }
      for _, label in ipairs(catLabels) do
        categories[#categories + 1] = { key = label, label = label }
      end
      local catButtons = {}
      editor.modelCategoryButtons = catButtons

      local function EnsureCatButton(idx)
        local b = catButtons[idx]
        if not b then
          b = CreateFrame("Button", nil, catContent, "UIPanelButtonTemplate")
          b:SetSize(90, 22)
          catButtons[idx] = b
        end
        return b
      end

      local cy = -4
      for i, cat in ipairs(categories) do
        local b = EnsureCatButton(i)
        b:ClearAllPoints()
        b:SetPoint("TOPLEFT", 0, cy)
        b:SetText(cat.label)
        b.__key = cat.key
        cy = cy - 26
      end
      catContent:SetSize(90, math.max(1, -cy + 6))

      local pageSize = 100
      local page = 1
      local catKey = "ALL"
      local search = ""
      local cachedFilterKey = nil
      local cachedFilterList = nil

      local function FilterEntries()
        local filterKey = table.concat({
          tostring(catKey or "ALL"),
          tostring(search or ""),
        }, "\31")
        if cachedFilterKey == filterKey and cachedFilterList then
          return cachedFilterList
        end
        local list = {}
        for _, entry in ipairs(entries) do
          if catKey == "ALL" or entry.cat == catKey then
            if search == "" or (entry.search and entry.search:find(search, 1, true)) then
              list[#list + 1] = entry
            end
          end
        end
        cachedFilterKey = filterKey
        cachedFilterList = list
        return list
      end

      local function Refresh()
        local list = FilterEntries()
        local total = #list
        local pages = math.max(1, math.ceil(total / pageSize))
        if page > pages then page = pages end
        if page < 1 then page = 1 end
        pageLabel:SetText("Page " .. page .. " / " .. pages)
        if pageEdit and not pageEdit:HasFocus() then
          pageEdit:SetText(tostring(page))
        end
        local startIndex = (page - 1) * pageSize + 1
        local yPos = -4
        for i = 1, pageSize do
          local item = list[startIndex + i - 1]
          local b = EnsureListButton(i)
          if item then
            b:ClearAllPoints()
            b:SetPoint("TOPLEFT", 0, yPos)
            b:SetText(item.label or ("Model " .. tostring(item.id)))
            b.__item = item
            if selectedItem and selectedItem.id == item.id then
              if b.SetTextColor then b:SetTextColor(0.30, 0.80, 1.00) end
              if b.__selBorder then b.__selBorder:Show() end
            else
              if b.SetTextColor then b:SetTextColor(1, 1, 1) end
              if b.__selBorder then b.__selBorder:Hide() end
            end
            b:Show()
            yPos = yPos - 24
          else
            b:Hide()
          end
        end
        listContent:SetSize(190, math.max(1, -yPos + 6))
        listScroll:SetVerticalScroll(0)
      end

      for _, b in ipairs(catButtons) do
        b:SetScript("OnClick", function()
          catKey = b.__key
          page = 1
          Refresh()
        end)
      end

      local function DoSearch()
        search = string.lower(searchBox:GetText() or "")
        page = 1
        Refresh()
      end

      searchBtn:SetScript("OnClick", DoSearch)
      searchBox:SetScript("OnEnterPressed", function(self)
        DoSearch()
        self:ClearFocus()
      end)
      searchBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)

      prevBtn:SetScript("OnClick", function()
        page = math.max(1, page - 1)
        Refresh()
      end)
      nextBtn:SetScript("OnClick", function()
        page = page + 1
        Refresh()
      end)
      pageEdit:SetScript("OnEnterPressed", function(self)
        local v = tonumber(self:GetText() or "")
        if v then page = v end
        Refresh()
        self:ClearFocus()
      end)
      pageEdit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)

      zoomSlider:SetValue(previewState.zoom)
      rotSlider:SetValue(previewState.facing)
      xSlider:SetValue(previewState.x)
      ySlider:SetValue(previewState.y)

      zoomSlider:SetScript("OnValueChanged", function(_, val)
        previewState.zoom = val
        ApplyPreviewTransform()
      end)
      rotSlider:SetScript("OnValueChanged", function(_, val)
        previewState.facing = val
        ApplyPreviewTransform()
      end)
      xSlider:SetScript("OnValueChanged", function(_, val)
        previewState.x = val
        ApplyPreviewTransform()
      end)
      ySlider:SetScript("OnValueChanged", function(_, val)
        previewState.y = val
        ApplyPreviewTransform()
      end)

      resetBtn:SetScript("OnClick", function()
        previewState.zoom = 1.0
        previewState.facing = 0
        previewState.x = 0
        previewState.y = 0
        zoomSlider:SetValue(1.0)
        rotSlider:SetValue(0)
        xSlider:SetValue(0)
        ySlider:SetValue(0)
        ApplyPreviewTransform()
      end)

      applyBtn:SetScript("OnClick", function()
        if not selectedItem then return end
        if onSelect then
          onSelect(selectedItem, {
            zoom = previewState.zoom,
            facing = previewState.facing,
            x = previewState.x,
            y = previewState.y,
          })
        end
        picker:Hide()
      end)

      listFrame:SetScript("OnLeave", function()
        if selectedItem then ApplyPreviewTransform() end
      end)
      previewFrame:SetScript("OnEnter", function()
        if selectedItem then ApplyPreviewTransform() end
      end)

      if editor.ApplyTheme then editor:ApplyTheme() end
      picker.Refresh = Refresh
      picker:HookScript("OnShow", function()
        Refresh()
        if selectedItem then SetPreview(selectedItem) end
      end)
      Refresh()
      if selectedItem then SetPreview(selectedItem) end
      editor.modelPicker = picker
    end

    editor.modelPicker.onSelect = onSelect
    editor.modelPicker:Show()
    if editor.modelPicker.Refresh then
      editor.modelPicker:Refresh()
    end
  end

  if iconPickBtn then
    iconPickBtn:SetScript("OnClick", function()
      ShowIconPicker(function(icon)
        if not icon or not icon.texture then return end
        SetPanelBackground(icon.texture, "ICON", icon.label)
      end)
    end)
  end

  if texturePickBtn then
    texturePickBtn:SetScript("OnClick", function()
      ShowTexturePicker(function(item)
        if not item or not item.path then return end
        SetPanelBackground(item.path, "TEX", item.label)
      end)
    end)
  end

  if modelPickBtn then
    modelPickBtn:SetScript("OnClick", function()
      ShowModelPicker(function(item, settings)
        if not item or not item.id then return end
        SetPanelBackground(item.id, "MODEL", item.label, settings)
      end)
    end)
  end

  return {
    BuildCustomBackgroundOptions = BuildCustomBackgroundOptions,
    CopyTexCoord = CopyTexCoord,
    SameTexCoord = SameTexCoord,
    FindTalentOptionForPanel = FindTalentOptionForPanel,
    UpdateBgPreview = UpdateBgPreview,
    SetPanelBackground = SetPanelBackground,
  }
end

INTERNAL_API.LIDPANELS_CreatePanelAssetPickers_Impl = CreatePanelAssetPickers
