-- LidCouncil_Editor_BackgroundManagement.lua
-- Split from Editor.lua to isolate background/category management.

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

local getDeps = INTERNAL_API.LIDCOUNCIL_GetEditorBackgroundManagementDeps_Impl
if type(getDeps) ~= "function" then
  error("LidCouncil missing editor background management dependencies.")
end

local deps = getDeps()
local RootDB = deps.RootDB
local Print = deps.Print
local LogSilent = deps.LogSilent
local TrimName = deps.TrimName
local ClampNameLen = deps.ClampNameLen
local CreatePurpleOutline = deps.CreatePurpleOutline

local MAX_BACKGROUND_NAME = deps.MAX_BACKGROUND_NAME
local MAX_BG_CATEGORY_NAME = deps.MAX_BG_CATEGORY_NAME
local MAX_BG_CATEGORY_GROUP_NAME = deps.MAX_BG_CATEGORY_GROUP_NAME

local function GetEditor()
  return deps.GetEditor()
end

local function GetSelectedBackgroundName()
  return deps.GetSelectedBackgroundName()
end

local function SetSelectedBackgroundName(value)
  deps.SetSelectedBackgroundName(value)
end

local function GetSelectedBackgroundCategory()
  return deps.GetSelectedBackgroundCategory()
end

local function SetSelectedBackgroundCategory(value)
  deps.SetSelectedBackgroundCategory(value)
end

local function GetSelectedBackgroundCategoryGroup()
  return deps.GetSelectedBackgroundCategoryGroup()
end

local function SetSelectedBackgroundCategoryGroup(value)
  deps.SetSelectedBackgroundCategoryGroup(value)
end

local backgroundCategoryGroupsNormalizedRoot
local backgroundCategoryGroupMembershipRoot
local backgroundCategoryGroupMembershipByCategory
local sortedBackgroundNamesByList = setmetatable({}, { __mode = "k" })

local function InvalidateSortedBackgroundNames(list)
  if not list then
    sortedBackgroundNamesByList = setmetatable({}, { __mode = "k" })
    return
  end
  sortedBackgroundNamesByList[list] = nil
end

local function GetSortedBackgroundNames(list)
  if type(list) ~= "table" then
    return {}
  end
  local cached = sortedBackgroundNamesByList[list]
  if cached then
    return cached
  end
  local names = {}
  for _, name in ipairs(list) do
    names[#names + 1] = name
  end
  table.sort(names, function(a, b)
    return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
  end)
  sortedBackgroundNamesByList[list] = names
  return names
end

local function NormalizeBackgroundName(raw)
  local name = TrimName(raw or "")
  if name == "" then
    return ""
  end
  name = name:gsub("%.tga$", "")
  name = name:gsub("[^%w%s%-%_]", "")
  name = name:gsub("%s+", " ")
  return name
end

local function EnsureBackgroundCategories()
  local root = RootDB()
  root.backgroundCategories = root.backgroundCategories or {}
  return root.backgroundCategories
end

local function ListBackgroundCategories()
  return EnsureBackgroundCategories()
end

local function ListBackgrounds(categoryName)
  local categories = EnsureBackgroundCategories()
  if not categoryName or categoryName == "" then
    return {}
  end
  for _, category in ipairs(categories) do
    if category.name == categoryName then
      category.items = category.items or {}
      return category.items
    end
  end
  return {}
end

local function FindBackgroundIndex(list, name)
  local needle = string.lower(tostring(name or ""))
  for index, value in ipairs(list or {}) do
    if string.lower(tostring(value or "")) == needle then
      return index
    end
  end
  return nil
end

local function FindCategoryIndex(list, name)
  local needle = string.lower(tostring(name or ""))
  for index, value in ipairs(list or {}) do
    if string.lower(tostring(type(value) == "table" and value.name or "")) == needle then
      return index
    end
  end
  return nil
end

local function EnsureBackgroundCategoryGroups()
  local root = RootDB()
  root.backgroundCategoryGroups = root.backgroundCategoryGroups or {}
  if backgroundCategoryGroupsNormalizedRoot == root
    and backgroundCategoryGroupMembershipRoot == root
    and type(root.backgroundCategoryGroups) == "table"
  then
    return root.backgroundCategoryGroups
  end

  local groups = root.backgroundCategoryGroups
  local normalized = {}
  local seenNames = {}
  local categories = ListBackgroundCategories()
  local membership = {}

  for _, group in ipairs(groups) do
    if type(group) == "table" then
      local rawName = TrimName(group.name)
      local name = ClampNameLen(rawName, MAX_BG_CATEGORY_GROUP_NAME, "Category group")
      local key = string.lower(name or "")
      if name ~= "" and not seenNames[key] then
        seenNames[key] = true
        local clean = {
          name = name,
          collapsed = group.collapsed == true,
          categories = {},
        }
        local seenCategories = {}
        for _, categoryName in ipairs(group.categories or {}) do
          local resolvedName = tostring(categoryName or "")
          if resolvedName ~= "" and not seenCategories[resolvedName] and FindCategoryIndex(categories, resolvedName) then
            clean.categories[#clean.categories + 1] = resolvedName
            seenCategories[resolvedName] = true
            membership[resolvedName] = name
          end
        end
        table.sort(clean.categories, function(a, b)
          return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
        end)
        normalized[#normalized + 1] = clean
      end
    end
  end

  table.sort(normalized, function(a, b)
    return string.lower(tostring((a and a.name) or "")) < string.lower(tostring((b and b.name) or ""))
  end)

  root.backgroundCategoryGroups = normalized
  backgroundCategoryGroupsNormalizedRoot = root
  backgroundCategoryGroupMembershipRoot = root
  backgroundCategoryGroupMembershipByCategory = membership
  return normalized
end

local function FindBackgroundCategoryGroupIndex(groups, groupName)
  local needle = string.lower(tostring(groupName or ""))
  for index, group in ipairs(groups or {}) do
    if string.lower(tostring(group.name or "")) == needle then
      return index
    end
  end
  return nil
end

local function GetBackgroundCategoryGroupName(categoryName)
  if not categoryName or categoryName == "" then
    return nil
  end
  local root = RootDB()
  if backgroundCategoryGroupMembershipRoot ~= root then
    EnsureBackgroundCategoryGroups()
  end
  return backgroundCategoryGroupMembershipByCategory and backgroundCategoryGroupMembershipByCategory[categoryName] or nil
end

local function RemoveBackgroundCategoryFromAllGroups(categoryName, groups)
  if not categoryName or categoryName == "" then
    return nil
  end
  groups = groups or EnsureBackgroundCategoryGroups()
  local root = RootDB()
  local removedFrom = nil
  for _, group in ipairs(groups) do
    for index = #group.categories, 1, -1 do
      if group.categories[index] == categoryName then
        removedFrom = group.name
        table.remove(group.categories, index)
      end
    end
  end
  if backgroundCategoryGroupMembershipRoot == root and backgroundCategoryGroupMembershipByCategory then
    backgroundCategoryGroupMembershipByCategory[categoryName] = nil
  end
  return removedFrom
end

local function AssignBackgroundCategoryToGroup(categoryName, groupName)
  if not categoryName or categoryName == "" then
    return false
  end
  local categories = ListBackgroundCategories()
  if not FindCategoryIndex(categories, categoryName) then
    return false
  end

  local groups = EnsureBackgroundCategoryGroups()
  if not groupName or groupName == "" then
    RemoveBackgroundCategoryFromAllGroups(categoryName, groups)
    backgroundCategoryGroupsNormalizedRoot = RootDB()
    return true
  end

  local index = FindBackgroundCategoryGroupIndex(groups, groupName)
  if not index then
    return false
  end

  RemoveBackgroundCategoryFromAllGroups(categoryName, groups)
  local group = groups[index]
  group.collapsed = false
  group.categories = group.categories or {}
  group.categories[#group.categories + 1] = categoryName
  table.sort(group.categories, function(a, b)
    return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
  end)
  backgroundCategoryGroupsNormalizedRoot = RootDB()
  if backgroundCategoryGroupMembershipRoot == RootDB() and backgroundCategoryGroupMembershipByCategory then
    backgroundCategoryGroupMembershipByCategory[categoryName] = group.name
  end
  return true
end

local function ToggleBackgroundCategoryGroupCollapsed(groupName)
  local groups = EnsureBackgroundCategoryGroups()
  local index = FindBackgroundCategoryGroupIndex(groups, groupName)
  if not index then
    return false
  end
  groups[index].collapsed = not groups[index].collapsed
  return true
end

local function BuildVisibleBackgroundCategoryRows(selectedCategoryName)
  local rows = {}
  local grouped = {}
  local groups = EnsureBackgroundCategoryGroups()
  local categories = ListBackgroundCategories()
  local categoryExists = {}

  for _, category in ipairs(categories) do
    if category and category.name then
      categoryExists[category.name] = true
    end
  end

  for _, group in ipairs(groups) do
    local childNames = {}
    for _, categoryName in ipairs(group.categories or {}) do
      if categoryExists[categoryName] then
        childNames[#childNames + 1] = categoryName
        grouped[categoryName] = group.name
      end
    end

    local containsSelected = false
    for _, categoryName in ipairs(childNames) do
      if categoryName == selectedCategoryName then
        containsSelected = true
      end
    end

    rows[#rows + 1] = {
      type = "group",
      key = "bg-group:" .. string.lower(group.name or ""),
      name = group.name,
      collapsed = group.collapsed == true,
      childCount = #childNames,
      containsSelected = containsSelected,
    }

    if group.collapsed ~= true then
      for _, categoryName in ipairs(childNames) do
        rows[#rows + 1] = {
          type = "category",
          key = "bg-category:" .. categoryName,
          name = categoryName,
          groupName = group.name,
        }
      end
    end
  end

  local ungrouped = {}
  for _, category in ipairs(categories) do
    if category and category.name and not grouped[category.name] then
      ungrouped[#ungrouped + 1] = category.name
    end
  end
  table.sort(ungrouped, function(a, b)
    return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
  end)
  for _, categoryName in ipairs(ungrouped) do
    rows[#rows + 1] = {
      type = "category",
      key = "bg-category:" .. categoryName,
      name = categoryName,
      groupName = nil,
    }
  end

  return rows
end

local function GetBackgroundCategoryFocusKey(selectedCategoryName, selectedGroupName)
  if selectedGroupName and selectedGroupName ~= "" then
    return "bg-group:" .. string.lower(selectedGroupName)
  end
  if not selectedCategoryName or selectedCategoryName == "" then
    return nil
  end
  local groupName = GetBackgroundCategoryGroupName(selectedCategoryName)
  if groupName then
    local groups = EnsureBackgroundCategoryGroups()
    local index = FindBackgroundCategoryGroupIndex(groups, groupName)
    if index and groups[index] and groups[index].collapsed then
      return "bg-group:" .. string.lower(groupName)
    end
  end
  return "bg-category:" .. selectedCategoryName
end

local function SelectBackground(name)
  SetSelectedBackgroundName(name)
  SetSelectedBackgroundCategoryGroup(nil)
  local editor = GetEditor()
  if editor and editor.RefreshBackgroundListDetailsSection then
    editor:RefreshBackgroundListDetailsSection()
  elseif editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
end

local function CreateBackgroundNamed(raw)
  local name = NormalizeBackgroundName(raw)
  name = ClampNameLen(name, MAX_BACKGROUND_NAME, "Background")
  if name == "" then
    Print("Please enter a background name.")
    return false
  end
  if raw and TrimName(raw) ~= name then
    Print("Background name adjusted to: " .. name)
  end
  local category = GetSelectedBackgroundCategory()
  if not category or category == "" then
    StaticPopup_Show("LIDCOUNCIL_NEED_BG_CATEGORY")
    return false
  end
  local list = ListBackgrounds(category)
  if FindBackgroundIndex(list, name) then
    Print("Background already exists: " .. name)
    return false
  end
  table.insert(list, name)
  InvalidateSortedBackgroundNames(list)
  local editor = GetEditor()
  if editor then
    editor.customBgOptions = nil
  end
  SelectBackground(name)
  return true
end

local function RenameBackground(oldName, rawNewName)
  if not oldName or oldName == "" then
    return
  end
  local newName = NormalizeBackgroundName(rawNewName)
  newName = ClampNameLen(newName, MAX_BACKGROUND_NAME, "Background")
  if newName == "" then
    Print("Please enter a background name.")
    return
  end
  if rawNewName and TrimName(rawNewName) ~= newName then
    Print("Background name adjusted to: " .. newName)
  end
  if oldName == newName then
    return
  end
  local list = ListBackgrounds(GetSelectedBackgroundCategory())
  if FindBackgroundIndex(list, newName) then
    Print("Background already exists: " .. newName)
    return
  end
  local index = FindBackgroundIndex(list, oldName)
  if not index then
    return
  end
  list[index] = newName
  InvalidateSortedBackgroundNames(list)
  local editor = GetEditor()
  if editor then
    editor.customBgOptions = nil
  end
  SelectBackground(newName)
end

local function DeleteBackground(name)
  if not name or name == "" then
    return
  end
  local list = ListBackgrounds(GetSelectedBackgroundCategory())
  local index = FindBackgroundIndex(list, name)
  if not index then
    return
  end
  table.remove(list, index)
  InvalidateSortedBackgroundNames(list)
  local editor = GetEditor()
  if editor then
    editor.customBgOptions = nil
  end
  if GetSelectedBackgroundName() == name then
    SetSelectedBackgroundName(nil)
  end
end

local function CreateBackgroundCategoryNamed(raw)
  local name = TrimName(raw or "")
  name = ClampNameLen(name, MAX_BG_CATEGORY_NAME, "Category")
  if name == "" then
    Print("Please enter a category name.")
    return false
  end
  local categories = ListBackgroundCategories()
  if FindCategoryIndex(categories, name) then
    Print("Category already exists: " .. name)
    return false
  end
  categories[#categories + 1] = { name = name, items = {} }
  SetSelectedBackgroundCategory(name)
  SetSelectedBackgroundCategoryGroup(nil)
  SetSelectedBackgroundName(nil)
  local editor = GetEditor()
  if editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

local function RenameBackgroundCategory(oldName, rawNewName)
  if not oldName or oldName == "" then
    return false
  end
  local newName = TrimName(rawNewName or "")
  newName = ClampNameLen(newName, MAX_BG_CATEGORY_NAME, "Category")
  if newName == "" then
    Print("Please enter a category name.")
    return false
  end
  if oldName == newName then
    return true
  end

  local categories = ListBackgroundCategories()
  if FindCategoryIndex(categories, newName) then
    Print("Category already exists: " .. newName)
    return false
  end

  local index = FindCategoryIndex(categories, oldName)
  if not index then
    return false
  end

  categories[index].name = newName
  local groups = EnsureBackgroundCategoryGroups()
  for _, group in ipairs(groups) do
    for groupIndex, categoryName in ipairs(group.categories or {}) do
      if categoryName == oldName then
        group.categories[groupIndex] = newName
      end
    end
    table.sort(group.categories, function(a, b)
      return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
    end)
  end
  backgroundCategoryGroupsNormalizedRoot = RootDB()
  if backgroundCategoryGroupMembershipRoot == RootDB() and backgroundCategoryGroupMembershipByCategory then
    backgroundCategoryGroupMembershipByCategory[newName] = backgroundCategoryGroupMembershipByCategory[oldName]
    backgroundCategoryGroupMembershipByCategory[oldName] = nil
  end
  if GetSelectedBackgroundCategory() == oldName then
    SetSelectedBackgroundCategory(newName)
  end
  table.sort(groups, function(a, b)
    return string.lower(tostring((a and a.name) or "")) < string.lower(tostring((b and b.name) or ""))
  end)
  local editor = GetEditor()
  if editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

local function DeleteBackgroundCategory(name)
  if not name or name == "" then
    return
  end
  local categories = ListBackgroundCategories()
  local index = FindCategoryIndex(categories, name)
  if not index then
    return
  end
  RemoveBackgroundCategoryFromAllGroups(name)
  table.remove(categories, index)
  local editor = GetEditor()
  if editor then
    editor.customBgOptions = nil
  end
  if GetSelectedBackgroundCategory() == name then
    SetSelectedBackgroundCategory(nil)
    SetSelectedBackgroundName(nil)
  end
  if editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
end

local function CreateBackgroundCategoryGroupNamed(raw)
  local name = TrimName(raw or "")
  name = ClampNameLen(name, MAX_BG_CATEGORY_GROUP_NAME, "Category group")
  if name == "" then
    Print("Please enter a category group name.")
    return false
  end

  local groups = EnsureBackgroundCategoryGroups()
  if FindBackgroundCategoryGroupIndex(groups, name) then
    Print("Category group already exists: " .. name)
    return false
  end

  groups[#groups + 1] = {
    name = name,
    collapsed = false,
    categories = {},
  }
  table.sort(groups, function(a, b)
    return string.lower(tostring((a and a.name) or "")) < string.lower(tostring((b and b.name) or ""))
  end)
  backgroundCategoryGroupsNormalizedRoot = RootDB()
  SetSelectedBackgroundCategoryGroup(name)
  SetSelectedBackgroundCategory(nil)
  SetSelectedBackgroundName(nil)
  local editor = GetEditor()
  if editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

local function RenameBackgroundCategoryGroup(oldName, rawNewName)
  local newName = TrimName(rawNewName or "")
  newName = ClampNameLen(newName, MAX_BG_CATEGORY_GROUP_NAME, "Category group")
  if not oldName or oldName == "" then
    return false
  end
  if newName == "" then
    Print("Please enter a category group name.")
    return false
  end
  if oldName == newName then
    return true
  end

  local groups = EnsureBackgroundCategoryGroups()
  local index = FindBackgroundCategoryGroupIndex(groups, oldName)
  if not index then
    return false
  end
  local existingIndex = FindBackgroundCategoryGroupIndex(groups, newName)
  if existingIndex and existingIndex ~= index then
    Print("Category group already exists: " .. newName)
    return false
  end

  local renamedGroup = groups[index]
  renamedGroup.name = newName
  table.sort(groups, function(a, b)
    return string.lower(tostring((a and a.name) or "")) < string.lower(tostring((b and b.name) or ""))
  end)
  backgroundCategoryGroupsNormalizedRoot = RootDB()
  if backgroundCategoryGroupMembershipRoot == RootDB() and backgroundCategoryGroupMembershipByCategory then
    for _, categoryName in ipairs(renamedGroup.categories or {}) do
      backgroundCategoryGroupMembershipByCategory[categoryName] = newName
    end
  end
  if GetSelectedBackgroundCategoryGroup() == oldName then
    SetSelectedBackgroundCategoryGroup(newName)
  end
  local editor = GetEditor()
  if editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

local function DeleteBackgroundCategoryGroup(name)
  if not name or name == "" then
    return false
  end
  local groups = EnsureBackgroundCategoryGroups()
  local index = FindBackgroundCategoryGroupIndex(groups, name)
  if not index then
    return false
  end
  if backgroundCategoryGroupMembershipRoot == RootDB() and backgroundCategoryGroupMembershipByCategory then
    for _, categoryName in ipairs(groups[index].categories or {}) do
      backgroundCategoryGroupMembershipByCategory[categoryName] = nil
    end
  end
  table.remove(groups, index)
  backgroundCategoryGroupsNormalizedRoot = RootDB()
  if GetSelectedBackgroundCategoryGroup() == name then
    SetSelectedBackgroundCategoryGroup(nil)
  end
  local editor = GetEditor()
  if editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

local ClearBackgroundCategoryDragState
local CancelBackgroundCategoryDrag
local CompleteBackgroundCategoryDrag
local MoveBackgroundToCategory
local EnsureBackgroundDragBadge

MoveBackgroundToCategory = function(name, sourceCategory, targetCategory)
  local backgroundName = NormalizeBackgroundName(name)
  local sourceName = TrimName(sourceCategory or "")
  local targetName = TrimName(targetCategory or "")
  if backgroundName == "" or sourceName == "" or targetName == "" then
    return false
  end

  local sourceList = ListBackgrounds(sourceName)
  local sourceIndex = FindBackgroundIndex(sourceList, backgroundName)
  if not sourceIndex then
    return false
  end

  local editor = GetEditor()
  if sourceName == targetName then
    local needsRefresh = (GetSelectedBackgroundCategory() ~= sourceName)
      or (GetSelectedBackgroundCategoryGroup() ~= nil)
      or (GetSelectedBackgroundName() ~= backgroundName)
    SetSelectedBackgroundCategory(sourceName)
    SetSelectedBackgroundCategoryGroup(nil)
    SetSelectedBackgroundName(backgroundName)
    if editor and editor.RestoreBackgroundsCurrentLabel then
      editor:RestoreBackgroundsCurrentLabel()
    end
    if needsRefresh and editor and editor.RefreshBackgroundListDetailsSection then
      editor:RefreshBackgroundListDetailsSection()
    elseif needsRefresh and editor and editor.RefreshBackgroundsSection then
      editor:RefreshBackgroundsSection()
    elseif needsRefresh and editor and editor.Refresh then
      editor:Refresh()
    end
    return true
  end

  local targetList = ListBackgrounds(targetName)
  if FindBackgroundIndex(targetList, backgroundName) then
    Print("Category \"" .. targetName .. "\" already contains the background \"" .. backgroundName .. "\".")
    local needsRefresh = (GetSelectedBackgroundCategory() ~= sourceName)
      or (GetSelectedBackgroundCategoryGroup() ~= nil)
      or (GetSelectedBackgroundName() ~= backgroundName)
    SetSelectedBackgroundCategory(sourceName)
    SetSelectedBackgroundCategoryGroup(nil)
    SetSelectedBackgroundName(backgroundName)
    if editor and editor.RestoreBackgroundsCurrentLabel then
      editor:RestoreBackgroundsCurrentLabel()
    end
    if needsRefresh and editor and editor.RefreshBackgroundListDetailsSection then
      editor:RefreshBackgroundListDetailsSection()
    elseif needsRefresh and editor and editor.RefreshBackgroundsSection then
      editor:RefreshBackgroundsSection()
    elseif needsRefresh and editor and editor.Refresh then
      editor:Refresh()
    end
    return false
  end

  table.insert(targetList, backgroundName)
  table.remove(sourceList, sourceIndex)
  InvalidateSortedBackgroundNames(sourceList)
  InvalidateSortedBackgroundNames(targetList)

  SetSelectedBackgroundCategory(sourceName)
  SetSelectedBackgroundCategoryGroup(nil)
  SetSelectedBackgroundName(nil)
  Print("Moved background \"" .. backgroundName .. "\" to category \"" .. targetName .. "\".")
  if editor and editor.RefreshBackgroundListDetailsSection then
    editor:RefreshBackgroundListDetailsSection()
  elseif editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

EnsureBackgroundDragBadge = function()
  local editor = GetEditor()
  if not editor then
    return nil
  end
  if editor.__bgDragBadge then
    return editor.__bgDragBadge
  end

  local badge = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
  badge:SetFrameStrata("TOOLTIP")
  badge:SetFrameLevel(250)
  badge:SetClampedToScreen(true)
  badge:SetSize(260, 36)
  badge:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
  })
  badge:SetBackdropColor(0.04, 0.05, 0.08, 0.97)
  badge:SetBackdropBorderColor(0.6588, 0.4784, 0.9098, 0.98)
  badge:Hide()

  local text = badge:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  text:SetPoint("LEFT", 10, 0)
  text:SetPoint("RIGHT", -10, 0)
  text:SetJustifyH("LEFT")
  text:SetTextColor(0.98, 0.94, 1.00, 1.00)
  badge.text = text

  editor.__bgDragBadge = badge
  return badge
end

ClearBackgroundCategoryDragState = function()
  local editor = GetEditor()
  if not editor then
    return nil
  end
  local state = editor.__backgroundDrag
  if not state then
    return nil
  end

  if state.sourceButton and state.sourceButton.SetAlpha then
    state.sourceButton:SetAlpha(1)
  end
  if state.sourceButton and state.sourceButton.__bgDragSourceBorder then
    state.sourceButton.__bgDragSourceBorder:Hide()
  end
  if state.hoveredButton and state.hoveredButton.__bgDragDropBorder then
    state.hoveredButton.__bgDragDropBorder:Hide()
  end
  if editor.__bgDragBadge then
    editor.__bgDragBadge:Hide()
  end
  if editor.__bgDragMonitor then
    editor.__bgDragMonitor:Hide()
  end

  editor.__backgroundDrag = nil
  return state
end

CancelBackgroundCategoryDrag = function(skipRefresh)
  local state = ClearBackgroundCategoryDragState()
  if not state then
    return false
  end
  local editor = GetEditor()
  if editor and editor.RestoreBackgroundsCurrentLabel then
    editor:RestoreBackgroundsCurrentLabel()
  end
  if (not skipRefresh) and editor and editor.ApplyTheme then
    editor:ApplyTheme()
  end
  return true
end

CompleteBackgroundCategoryDrag = function(targetCategory)
  local state = ClearBackgroundCategoryDragState()
  if not state then
    return false
  end
  local editor = GetEditor()
  if not targetCategory or targetCategory == "" then
    if editor and editor.RestoreBackgroundsCurrentLabel then
      editor:RestoreBackgroundsCurrentLabel()
    end
    if editor and editor.ApplyTheme then
      editor:ApplyTheme()
    end
    return false
  end
  return MoveBackgroundToCategory(state.name, state.sourceCategory, targetCategory)
end

local function EnsureBackgroundDragMonitor()
  local editor = GetEditor()
  if not editor then
    return nil
  end
  if editor.__bgDragMonitor then
    return editor.__bgDragMonitor
  end

  local monitor = CreateFrame("Frame", nil, editor)
  local badge = EnsureBackgroundDragBadge()
  monitor:Hide()
  monitor:SetAllPoints(editor)
  monitor:SetScript("OnUpdate", function(self)
    local currentEditor = GetEditor()
    local state = currentEditor and currentEditor.__backgroundDrag
    if not state then
      if badge then
        badge:Hide()
      end
      self:Hide()
      return
    end

    local hoveredButton = nil
    if type(MouseIsOver) == "function" then
      for _, button in ipairs(currentEditor.bgCatDropButtons or currentEditor.bgCatButtons or {}) do
        if button and button.IsShown and button:IsShown() and MouseIsOver(button) then
          hoveredButton = button
          break
        end
      end
    end

    if hoveredButton ~= state.hoveredButton then
      if state.hoveredButton and state.hoveredButton.__bgDragDropBorder then
        state.hoveredButton.__bgDragDropBorder:Hide()
      end
      state.hoveredButton = hoveredButton
      if hoveredButton and hoveredButton.__bgDragDropBorder then
        hoveredButton.__bgDragDropBorder:Show()
      end
    end

    if badge then
      local scale = (UIParent and UIParent.GetEffectiveScale and UIParent:GetEffectiveScale()) or 1
      if scale == 0 then
        scale = 1
      end
      local cursorX, cursorY = GetCursorPosition()
      if cursorX and cursorY then
        cursorX = cursorX / scale
        cursorY = cursorY / scale
        badge:ClearAllPoints()
        badge:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", cursorX + 18, cursorY - 16)
      end
      local targetName = hoveredButton and hoveredButton.__backgroundCategoryName or nil
      if targetName and targetName ~= "" then
        badge.text:SetText("Moving: " .. state.name .. "  ->  " .. targetName)
      else
        badge.text:SetText("Moving: " .. state.name)
      end
      badge:Show()
    end

    local leftDown = (IsMouseButtonDown and IsMouseButtonDown("LeftButton")) or false
    if leftDown then
      return
    end

    self:Hide()
    if hoveredButton and hoveredButton.__backgroundCategoryName then
      CompleteBackgroundCategoryDrag(hoveredButton.__backgroundCategoryName)
    else
      CancelBackgroundCategoryDrag()
    end
  end)

  editor.__bgDragMonitor = monitor
  return monitor
end

local function BeginBackgroundCategoryDrag(name, sourceCategory, sourceButton)
  local editor = GetEditor()
  if not editor or not sourceButton then
    return false
  end
  local backgroundName = NormalizeBackgroundName(name)
  local sourceName = TrimName(sourceCategory or "")
  if backgroundName == "" or sourceName == "" then
    return false
  end

  CancelBackgroundCategoryDrag(true)

  sourceButton.__bgDragSourceBorder = sourceButton.__bgDragSourceBorder or CreatePurpleOutline(sourceButton)
  sourceButton.__bgDragSourceBorder:Show()
  if sourceButton.SetAlpha then
    sourceButton:SetAlpha(0.72)
  end

  editor.__backgroundDrag = {
    name = backgroundName,
    sourceCategory = sourceName,
    sourceButton = sourceButton,
    hoveredButton = nil,
  }

  if editor.backgroundsCurrentLabel then
    editor.backgroundsCurrentLabel:SetText("Dragging: " .. backgroundName .. "  |  Drop it on a category to move it.")
  end

  local monitor = EnsureBackgroundDragMonitor()
  if monitor then
    monitor:Show()
  end
  return true
end

local ClearBackgroundCategoryGroupDragState
local CancelBackgroundCategoryGroupDrag
local CompleteBackgroundCategoryGroupDrag
local MoveBackgroundCategoryToGroup
local EnsureBackgroundCategoryGroupDragBadge

MoveBackgroundCategoryToGroup = function(categoryName, sourceGroupName, targetGroupName)
  local name = TrimName(categoryName or "")
  local sourceName = TrimName(sourceGroupName or "")
  local targetName = TrimName(targetGroupName or "")
  if name == "" then
    return false
  end

  local editor = GetEditor()
  if sourceName == targetName then
    local resolvedGroupName = (targetName ~= "" and targetName or nil)
    local needsRefresh = (GetSelectedBackgroundCategory() ~= name)
      or (GetSelectedBackgroundCategoryGroup() ~= resolvedGroupName)
      or (GetSelectedBackgroundName() ~= nil)
    SetSelectedBackgroundCategory(name)
    SetSelectedBackgroundCategoryGroup(resolvedGroupName)
    SetSelectedBackgroundName(nil)
    if editor and editor.RestoreBackgroundsCurrentLabel then
      editor:RestoreBackgroundsCurrentLabel()
    end
    if needsRefresh and editor and editor.RefreshBackgroundsSection then
      editor:RefreshBackgroundsSection()
    elseif needsRefresh and editor and editor.Refresh then
      editor:Refresh()
    end
    return true
  end

  local ok = AssignBackgroundCategoryToGroup(name, targetName ~= "" and targetName or nil)
  if not ok then
    return false
  end

  SetSelectedBackgroundCategory(name)
  SetSelectedBackgroundCategoryGroup(nil)
  SetSelectedBackgroundName(nil)
  if targetName ~= "" then
    Print("Moved category \"" .. name .. "\" to group \"" .. targetName .. "\".")
  else
    Print("Removed category \"" .. name .. "\" from its group.")
  end
  if editor and editor.RefreshBackgroundsSection then
    editor:RefreshBackgroundsSection()
  elseif editor and editor.Refresh then
    editor:Refresh()
  end
  return true
end

EnsureBackgroundCategoryGroupDragBadge = function()
  local editor = GetEditor()
  if not editor then
    return nil
  end
  if editor.__bgCategoryGroupDragBadge then
    return editor.__bgCategoryGroupDragBadge
  end

  local badge = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
  badge:SetFrameStrata("TOOLTIP")
  badge:SetFrameLevel(250)
  badge:SetClampedToScreen(true)
  badge:SetSize(328, 42)
  badge:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
  })
  badge:SetBackdropColor(0.13, 0.08, 0.20, 0.98)
  badge:SetBackdropBorderColor(0.6588, 0.4784, 0.9098, 1.00)
  badge:Hide()

  local text = badge:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  text:SetPoint("LEFT", 12, 0)
  text:SetPoint("RIGHT", -12, 0)
  text:SetJustifyH("LEFT")
  text:SetTextColor(0.98, 0.94, 1.00, 1.00)
  badge.text = text

  editor.__bgCategoryGroupDragBadge = badge
  return badge
end

ClearBackgroundCategoryGroupDragState = function()
  local editor = GetEditor()
  if not editor then
    return nil
  end
  local state = editor.__backgroundCategoryGroupDrag
  if not state then
    return nil
  end

  if state.sourceButton and state.sourceButton.SetAlpha then
    state.sourceButton:SetAlpha(1)
  end
  if state.sourceButton and state.sourceButton.__bgCategoryGroupDragSourceBorder then
    state.sourceButton.__bgCategoryGroupDragSourceBorder:Hide()
  end
  for _, button in ipairs(editor.bgCatGroupButtons or {}) do
    if button and button.__bgCategoryGroupDragDropBorder then
      button.__bgCategoryGroupDragDropBorder:Hide()
      button.__bgCategoryGroupDragDropBorder:SetBackdropBorderColor(0.6588, 0.4784, 0.9098, 0.95)
    end
  end
  if editor.__bgCategoryGroupDragBadge then
    editor.__bgCategoryGroupDragBadge:Hide()
  end
  if editor.__bgCategoryGroupDragMonitor then
    editor.__bgCategoryGroupDragMonitor:Hide()
  end

  editor.__backgroundCategoryGroupDrag = nil
  return state
end

CancelBackgroundCategoryGroupDrag = function(skipRefresh)
  local state = ClearBackgroundCategoryGroupDragState()
  if not state then
    return false
  end
  local editor = GetEditor()
  if editor and editor.RestoreBackgroundsCurrentLabel then
    editor:RestoreBackgroundsCurrentLabel()
  end
  if (not skipRefresh) and editor and editor.ApplyTheme then
    editor:ApplyTheme()
  end
  return true
end

CompleteBackgroundCategoryGroupDrag = function(targetGroupName)
  local state = ClearBackgroundCategoryGroupDragState()
  if not state then
    return false
  end
  return MoveBackgroundCategoryToGroup(state.name, state.sourceGroupName, targetGroupName)
end

local function EnsureBackgroundCategoryGroupDragMonitor()
  local editor = GetEditor()
  if not editor then
    return nil
  end
  if editor.__bgCategoryGroupDragMonitor then
    return editor.__bgCategoryGroupDragMonitor
  end

  local monitor = CreateFrame("Frame", nil, editor)
  local badge = EnsureBackgroundCategoryGroupDragBadge()
  monitor:Hide()
  monitor:SetAllPoints(editor)
  monitor:SetScript("OnUpdate", function(self)
    local currentEditor = GetEditor()
    local state = currentEditor and currentEditor.__backgroundCategoryGroupDrag
    if not state then
      if badge then
        badge:Hide()
      end
      self:Hide()
      return
    end

    local hoveredButton = nil
    if type(MouseIsOver) == "function" then
      for _, button in ipairs(currentEditor.bgCatGroupButtons or {}) do
        if button and button.IsShown and button:IsShown() and MouseIsOver(button) then
          hoveredButton = button
          break
        end
      end
    end

    if hoveredButton ~= state.hoveredButton then
      if state.hoveredButton and state.hoveredButton.__bgCategoryGroupDragDropBorder then
        state.hoveredButton.__bgCategoryGroupDragDropBorder:SetBackdropBorderColor(0.6588, 0.4784, 0.9098, 0.42)
        state.hoveredButton.__bgCategoryGroupDragDropBorder:Show()
      end
      state.hoveredButton = hoveredButton
      if hoveredButton and hoveredButton.__bgCategoryGroupDragDropBorder then
        hoveredButton.__bgCategoryGroupDragDropBorder:SetBackdropBorderColor(0.6588, 0.4784, 0.9098, 1.00)
        hoveredButton.__bgCategoryGroupDragDropBorder:Show()
      end
    end

    local overList = false
    if type(MouseIsOver) == "function" and currentEditor.bgCatBg and MouseIsOver(currentEditor.bgCatBg) then
      overList = true
    end

    if badge then
      local scale = (UIParent and UIParent.GetEffectiveScale and UIParent:GetEffectiveScale()) or 1
      if scale == 0 then
        scale = 1
      end
      local cursorX, cursorY = GetCursorPosition()
      if cursorX and cursorY then
        cursorX = cursorX / scale
        cursorY = cursorY / scale
        badge:ClearAllPoints()
        badge:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", cursorX + 18, cursorY - 16)
      end
      local targetName = hoveredButton and hoveredButton.__backgroundCategoryGroupName or nil
      if targetName and targetName ~= "" then
        badge.text:SetText("Moving: " .. state.name .. "  ->  " .. targetName)
      elseif overList then
        badge.text:SetText("Moving: " .. state.name .. "  ->  No group")
      else
        badge.text:SetText("Moving: " .. state.name .. "  |  Drop it on a group")
      end
      badge:Show()
    end

    local leftDown = (IsMouseButtonDown and IsMouseButtonDown("LeftButton")) or false
    if leftDown then
      return
    end

    self:Hide()
    if hoveredButton and hoveredButton.__backgroundCategoryGroupName then
      CompleteBackgroundCategoryGroupDrag(hoveredButton.__backgroundCategoryGroupName)
    elseif overList then
      CompleteBackgroundCategoryGroupDrag(nil)
    else
      CancelBackgroundCategoryGroupDrag()
    end
  end)

  editor.__bgCategoryGroupDragMonitor = monitor
  return monitor
end

local function BeginBackgroundCategoryGroupDrag(categoryName, sourceGroupName, sourceButton)
  local editor = GetEditor()
  if not editor or not sourceButton then
    return false
  end
  local name = TrimName(categoryName or "")
  if name == "" then
    return false
  end

  CancelBackgroundCategoryGroupDrag(true)

  sourceButton.__bgCategoryGroupDragSourceBorder = sourceButton.__bgCategoryGroupDragSourceBorder or CreatePurpleOutline(sourceButton)
  sourceButton.__bgCategoryGroupDragSourceBorder:Show()
  if sourceButton.SetAlpha then
    sourceButton:SetAlpha(0.72)
  end

  editor.__backgroundCategoryGroupDrag = {
    name = name,
    sourceGroupName = TrimName(sourceGroupName or ""),
    sourceButton = sourceButton,
    hoveredButton = nil,
  }

  for _, button in ipairs(editor.bgCatGroupButtons or {}) do
    if button and button.__bgCategoryGroupDragDropBorder then
      button.__bgCategoryGroupDragDropBorder:SetBackdropBorderColor(0.6588, 0.4784, 0.9098, 0.42)
      button.__bgCategoryGroupDragDropBorder:Show()
    end
  end

  if editor.backgroundsCurrentLabel then
    editor.backgroundsCurrentLabel:SetText("Dragging: " .. name .. "  |  Drop it on a group or release inside the list to remove the group.")
  end

  local monitor = EnsureBackgroundCategoryGroupDragMonitor()
  if monitor then
    monitor:Show()
  end
  return true
end

INTERNAL_API.LIDCOUNCIL_EditorBackgroundManagement_Impl = {
  InvalidateSortedBackgroundNames = InvalidateSortedBackgroundNames,
  GetSortedBackgroundNames = GetSortedBackgroundNames,
  ListBackgroundCategories = ListBackgroundCategories,
  ListBackgrounds = ListBackgrounds,
  FindBackgroundIndex = FindBackgroundIndex,
  FindCategoryIndex = FindCategoryIndex,
  EnsureBackgroundCategoryGroups = EnsureBackgroundCategoryGroups,
  FindBackgroundCategoryGroupIndex = FindBackgroundCategoryGroupIndex,
  BuildVisibleBackgroundCategoryRows = BuildVisibleBackgroundCategoryRows,
  GetBackgroundCategoryFocusKey = GetBackgroundCategoryFocusKey,
  ToggleBackgroundCategoryGroupCollapsed = ToggleBackgroundCategoryGroupCollapsed,
  SelectBackground = SelectBackground,
  CreateBackgroundNamed = CreateBackgroundNamed,
  RenameBackground = RenameBackground,
  DeleteBackground = DeleteBackground,
  CreateBackgroundCategoryNamed = CreateBackgroundCategoryNamed,
  RenameBackgroundCategory = RenameBackgroundCategory,
  DeleteBackgroundCategory = DeleteBackgroundCategory,
  CreateBackgroundCategoryGroupNamed = CreateBackgroundCategoryGroupNamed,
  RenameBackgroundCategoryGroup = RenameBackgroundCategoryGroup,
  DeleteBackgroundCategoryGroup = DeleteBackgroundCategoryGroup,
  ClearBackgroundCategoryDragState = ClearBackgroundCategoryDragState,
  BeginBackgroundCategoryDrag = BeginBackgroundCategoryDrag,
  BeginBackgroundCategoryGroupDrag = BeginBackgroundCategoryGroupDrag,
}
