-- LidCouncil_Editor_CustomDropdown.lua
-- Reusable custom dropdown control for long editor lists.

local API = _G.LIDCOUNCIL_API
if type(API) ~= "table" then
  error("LidCouncil missing API table")
end
local INTERNAL_API = type(API.__internal) == "table" and API.__internal or {}

local function PublishApi(name, value)
  _G[name] = value
  API[name] = value
end

local function TrimText(value)
  if value == nil then
    return ""
  end
  return tostring(value):match("^%s*(.-)%s*$") or ""
end

local function MixColor(colorA, colorB, weightA)
  weightA = tonumber(weightA)
  if weightA == nil then
    weightA = 0.5
  end
  local weightB = 1 - weightA
  local a = type(colorA) == "table" and colorA or { 1, 1, 1, 1 }
  local b = type(colorB) == "table" and colorB or { 0, 0, 0, 1 }
  return {
    ((a[1] or 1) * weightA) + ((b[1] or 0) * weightB),
    ((a[2] or 1) * weightA) + ((b[2] or 0) * weightB),
    ((a[3] or 1) * weightA) + ((b[3] or 0) * weightB),
    ((a[4] or 1) * weightA) + ((b[4] or 1) * weightB),
  }
end

local function ResolveDropdownThemePalette()
  local paletteResolver = type(INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl) == "function" and INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl or nil
  local palette = paletteResolver and paletteResolver() or nil
  if type(palette) == "table" then
    return palette
  end
  local active = { 0.74, 0.22, 0.30, 0.92 }
  local border = { 0.86, 0.58, 0.66, 0.95 }
  return {
    normal = { 0.08, 0.05, 0.06, 0.96 },
    active = active,
    border = border,
    text = { 1.00, 1.00, 1.00, 1.00 },
    dropdownButtonBg = { 0.08, 0.05, 0.06, 0.96 },
    dropdownPopupBg = { 0.07, 0.04, 0.05, 0.98 },
    dropdownCheckedText = MixColor(border, { 1, 1, 1, 1 }, 0.62),
    dropdownTitleText = MixColor(border, { 1, 1, 1, 1 }, 0.62),
  }
end

local function CreateCustomDropdownControl(editor, parent, opts)
  opts = opts or {}
  local dropdownWidth = tonumber(opts.width) or 220
  local buttonHeight = tonumber(opts.buttonHeight) or 24
  local rowHeight = tonumber(opts.rowHeight) or 18

  local root = CreateFrame("Frame", nil, parent)
  root.__lidcouncilCustomDropdown = true
  root.dropdownWidth = dropdownWidth
  root.buttonHeight = buttonHeight
  root.rowHeight = rowHeight
  root.maxVisibleEntries = math.max(5, math.floor(tonumber(opts.maxVisibleEntries) or 20))
  root.selectedValue = nil
  root.displayText = tostring(opts.text or "None")
  root.menuPage = 1
  root.searchText = ""
  root.enabled = true
  root:SetFrameStrata("DIALOG")
  root:SetToplevel(true)
  root:EnableMouse(true)
  root:SetSize(dropdownWidth, buttonHeight)

  local button = CreateFrame("Button", nil, root, "BackdropTemplate")
  button:SetFrameStrata("DIALOG")
  button:SetToplevel(true)
  button:EnableMouse(true)
  button:SetAllPoints(root)
  button:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
  })
  button:RegisterForClicks("LeftButtonUp")

  local highlight = button:CreateTexture(nil, "HIGHLIGHT")
  highlight:SetAllPoints()
  highlight:SetTexture("Interface\\Buttons\\WHITE8x8")

  local text = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  text:SetPoint("LEFT", 8, 0)
  text:SetPoint("RIGHT", -20, 0)
  text:SetJustifyH("LEFT")
  text:SetText(root.displayText)
  button.text = text

  local arrow = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  arrow:SetPoint("RIGHT", -8, 0)
  arrow:SetText("v")
  button.arrow = arrow

  local popup = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
  popup:SetClampedToScreen(true)
  popup:SetFrameStrata("TOOLTIP")
  popup:SetToplevel(true)
  popup:EnableMouseWheel(true)
  popup:Hide()
  popup:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
  })
  root.popup = popup

  local searchBox = CreateFrame("EditBox", nil, popup, "BackdropTemplate")
  searchBox:SetAutoFocus(false)
  searchBox:SetHeight(22)
  searchBox:EnableMouseWheel(true)
  searchBox:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
  })
  searchBox:SetTextInsets(6, 6, 0, 0)
  searchBox:SetFontObject(GameFontHighlightSmall)
  searchBox:Hide()
  root.searchBox = searchBox

  local searchHint = searchBox:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
  searchHint:SetPoint("LEFT", 8, 0)
  searchHint:SetPoint("RIGHT", -8, 0)
  searchHint:SetJustifyH("LEFT")
  searchHint:SetText("Search")
  searchBox.hint = searchHint

  local function UpdateSearchHint()
    if not searchBox or not searchBox.hint then
      return
    end
    local currentText = tostring(searchBox:GetText() or "")
    searchBox.hint:SetShown(currentText == "" and not searchBox:HasFocus())
  end

  local rows = {}

  local function EnsureRow(index)
    if rows[index] then
      return rows[index]
    end

    local row = CreateFrame("Button", nil, popup)
    row:SetHeight(rowHeight)
    if index == 1 then
      row:SetPoint("TOPLEFT", 6, -6)
      row:SetPoint("TOPRIGHT", -6, -6)
    else
      row:SetPoint("TOPLEFT", rows[index - 1], "BOTTOMLEFT", 0, 0)
      row:SetPoint("TOPRIGHT", rows[index - 1], "BOTTOMRIGHT", 0, 0)
    end
    row:EnableMouseWheel(true)

    local rowHighlight = row:CreateTexture(nil, "HIGHLIGHT")
    rowHighlight:SetAllPoints()
    rowHighlight:SetTexture("Interface\\Buttons\\WHITE8x8")
    row.rowHighlight = rowHighlight

    local selected = row:CreateTexture(nil, "ARTWORK")
    selected:SetTexture("Interface\\Buttons\\WHITE8x8")
    selected:SetSize(4, 12)
    selected:SetPoint("LEFT", 2, 0)
    selected:Hide()
    row.selectedMark = selected

    local icon = row:CreateTexture(nil, "ARTWORK")
    icon:SetSize(16, 16)
    icon:SetPoint("LEFT", 10, 0)
    icon:Hide()
    row.icon = icon

    local label = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    label:SetPoint("LEFT", 12, 0)
    label:SetPoint("RIGHT", -8, 0)
    label:SetJustifyH("LEFT")
    row.text = label

    row:SetScript("OnClick", function(self)
      local entry = self.entry
      if type(entry) ~= "table" or entry.disabled or entry.isTitle then
        return
      end
      if type(entry.func) == "function" then
        entry.func()
      end
      if entry.keepShownOnClick then
        root:RefreshMenu()
      else
        root:CloseMenu()
      end
    end)

    row:SetScript("OnMouseWheel", function(_, delta)
      local menu = root.activeMenu
      if type(menu) ~= "table" or type(menu.onMouseWheel) ~= "function" then
        return
      end
      local changed = menu.onMouseWheel(delta)
      if changed then
        root:RefreshMenu()
      end
    end)

    rows[index] = row
    return row
  end

  local function HidePopup()
    popup:Hide()
    root.activeMenu = nil
    root.searchText = ""
    if searchBox then
      root.__suppressSearchRefresh = true
      searchBox:SetText("")
      searchBox:ClearFocus()
      root.__suppressSearchRefresh = nil
      searchBox:Hide()
      UpdateSearchHint()
    end
    button.arrow:SetText("v")
    if editor and editor.__lidcouncilOpenDropdown == root then
      editor.__lidcouncilOpenDropdown = nil
    end
  end

  local function ApplyRowAppearance(row, entry)
    entry = entry or {}
    local palette = root.__themePalette or ResolveDropdownThemePalette()
    local active = palette.active or { 0.74, 0.22, 0.30, 0.92 }
    local border = palette.border or active
    local checkedText = palette.dropdownCheckedText or border
    local titleText = palette.dropdownTitleText or border
    local rowText = palette.dropdownRowText or palette.text or { 1.00, 1.00, 1.00, 1.00 }
    row.entry = entry
    row:Show()
    row.selectedMark:SetShown(entry.checked and (not entry.isTitle) and (not entry.notCheckable))
    if row.rowHighlight then
      row.rowHighlight:SetVertexColor(active[1] or 1, active[2] or 1, active[3] or 1, 0.12)
    end
    if row.selectedMark then
      row.selectedMark:SetVertexColor(border[1] or 1, border[2] or 1, border[3] or 1, border[4] or 0.95)
    end

    local iconTexture = entry.icon
    if iconTexture and iconTexture ~= "" then
      row.icon:SetTexture(iconTexture)
      row.icon:SetSize(tonumber(entry.iconSize) or 16, tonumber(entry.iconSize) or 16)
      local iconOffset = tonumber(entry.iconXOffset) or 0
      row.icon:ClearAllPoints()
      row.icon:SetPoint("LEFT", 10 + iconOffset, 0)
      row.icon:Show()
      row.text:ClearAllPoints()
      row.text:SetPoint("LEFT", row.icon, "RIGHT", 6, 0)
      row.text:SetPoint("RIGHT", -8, 0)
    else
      row.icon:Hide()
      row.text:ClearAllPoints()
      row.text:SetPoint("LEFT", 12, 0)
      row.text:SetPoint("RIGHT", -8, 0)
    end

    row.text:SetText(tostring(entry.text or ""))
    if entry.isTitle then
      row.text:SetTextColor(titleText[1] or 1, titleText[2] or 0.82, titleText[3] or 0.28, titleText[4] or 1.00)
    elseif entry.disabled then
      row.text:SetTextColor(0.55, 0.55, 0.55, 1.00)
    elseif entry.checked then
      row.text:SetTextColor(checkedText[1] or 1, checkedText[2] or 1, checkedText[3] or 1, checkedText[4] or 1.00)
    else
      row.text:SetTextColor(rowText[1] or 1, rowText[2] or 1, rowText[3] or 1, rowText[4] or 1.00)
    end
    row:EnableMouse((not entry.disabled) and (not entry.isTitle))
  end

  function root:ApplyTheme()
    local palette = ResolveDropdownThemePalette()
    if type(self.__lid_themeOverride) == "table" then
      local merged = {}
      for key, value in pairs(palette) do
        merged[key] = value
      end
      for key, value in pairs(self.__lid_themeOverride) do
        merged[key] = value
      end
      palette = merged
    end
    local buttonBg = palette.dropdownButtonBg or palette.normal or { 0.08, 0.05, 0.06, 0.96 }
    local popupBg = palette.dropdownPopupBg or palette.normal or { 0.07, 0.04, 0.05, 0.98 }
    local border = palette.border or palette.active or { 0.74, 0.22, 0.30, 0.95 }
    local active = palette.active or border
    local textColor = palette.text or { 1.00, 1.00, 1.00, 1.00 }
    if self.enabled == false then
      buttonBg = { 0.06, 0.06, 0.07, 0.72 }
      border = { 0.30, 0.30, 0.34, 0.82 }
      active = border
      textColor = { 0.48, 0.48, 0.52, 1.00 }
    end
    self.__themePalette = palette
    button:SetBackdropColor(buttonBg[1] or 0.08, buttonBg[2] or 0.05, buttonBg[3] or 0.06, buttonBg[4] or 0.96)
    button:SetBackdropBorderColor(border[1] or 1, border[2] or 1, border[3] or 1, border[4] or 0.95)
    highlight:SetVertexColor(active[1] or 1, active[2] or 1, active[3] or 1, 0.10)
    popup:SetBackdropColor(popupBg[1] or 0.07, popupBg[2] or 0.04, popupBg[3] or 0.05, popupBg[4] or 0.98)
    popup:SetBackdropBorderColor(border[1] or 1, border[2] or 1, border[3] or 1, border[4] or 0.96)
    if searchBox then
      searchBox:SetBackdropColor(buttonBg[1] or 0.08, buttonBg[2] or 0.05, buttonBg[3] or 0.06, 0.98)
      searchBox:SetBackdropBorderColor(border[1] or 1, border[2] or 1, border[3] or 1, border[4] or 0.95)
      searchBox:SetTextColor(textColor[1] or 1, textColor[2] or 1, textColor[3] or 1, textColor[4] or 1)
      if searchBox.hint then
        searchBox.hint:SetTextColor(0.62, 0.62, 0.66, 1.00)
      end
    end
    text:SetTextColor(textColor[1] or 1, textColor[2] or 1, textColor[3] or 1, textColor[4] or 1)
    arrow:SetTextColor(textColor[1] or 1, textColor[2] or 1, textColor[3] or 1, textColor[4] or 1)
    for _, row in ipairs(rows) do
      if row and row.entry then
        ApplyRowAppearance(row, row.entry)
      end
    end
  end

  function root:SetSelectedValue(value)
    self.selectedValue = value
  end

  function root:GetSelectedValue()
    return self.selectedValue
  end

  function root:SetText(value)
    self.displayText = tostring(value or "")
    button.text:SetText(self.displayText)
  end

  function root:SetDropdownWidth(value)
    local resolvedWidth = math.max(1, math.floor(tonumber(value) or self.dropdownWidth or 1))
    self.dropdownWidth = resolvedWidth
    self:SetSize(resolvedWidth, self.buttonHeight)
    button:SetSize(resolvedWidth, self.buttonHeight)
  end

  function root:GetText()
    return self.displayText
  end

  function root:SetMenuBuilder(fn)
    self.menuBuilder = fn
  end

  local function SyncButtonInteractivity()
    local isEnabled = root.enabled ~= false
    if root.EnableMouse then
      root:EnableMouse(isEnabled)
    end
    if button.Enable then
      if isEnabled then
        button:Enable()
      else
        button:Disable()
      end
    end
    if button.EnableMouse then
      button:EnableMouse(isEnabled)
    end
    if button.SetButtonState then
      button:SetButtonState(isEnabled and "NORMAL" or "DISABLED", false)
    end
  end

  function root:SetEnabled(enabled)
    self.enabled = enabled and true or false
    if self.enabled == false then
      self:CloseMenu()
    end
    SyncButtonInteractivity()
    self:ApplyTheme()
  end

  function root:Enable()
    self:SetEnabled(true)
  end

  function root:Disable()
    self:SetEnabled(false)
  end

  function root:RefreshMenu()
    if type(self.menuBuilder) ~= "function" then
      return false
    end

    local menu = self.menuBuilder(self) or {}
    local entries = type(menu.entries) == "table" and menu.entries or {}
    local popupWidth = tonumber(menu.width) or self.dropdownWidth
    local searchEnabled = menu.enableSearch and true or false
    local searchPlaceholder = tostring(menu.searchPlaceholder or "Search")
    local searchNeedle = string.lower(TrimText(self.searchText or ""))
    if searchEnabled and searchNeedle ~= "" then
      local filteredEntries = {}
      for _, entry in ipairs(entries) do
        local haystack = string.lower(tostring(entry.searchText or entry.text or ""))
        if haystack:find(searchNeedle, 1, true) then
          filteredEntries[#filteredEntries + 1] = entry
        end
      end
      entries = filteredEntries
    end
    local entryCount = #entries
    local maxVisibleEntries = math.max(5, math.floor(tonumber(menu.maxVisibleEntries) or self.maxVisibleEntries or 20))
    if (not menu.disableAutoPaging) and entryCount > maxVisibleEntries then
      local contentPerPage = math.max(5, maxVisibleEntries - 3)
      local pageCount = math.max(1, math.ceil(entryCount / contentPerPage))
      local desiredPage = tonumber(menu.page) or tonumber(self.menuPage) or 1
      if desiredPage < 1 then
        desiredPage = 1
      elseif desiredPage > pageCount then
        desiredPage = pageCount
      end
      self.menuPage = desiredPage
      if type(menu.onMouseWheel) ~= "function" then
        menu.onMouseWheel = function(delta)
          local nextPage = desiredPage + ((tonumber(delta) or 0) < 0 and 1 or -1)
          if nextPage < 1 then
            nextPage = 1
          elseif nextPage > pageCount then
            nextPage = pageCount
          end
          if nextPage == self.menuPage then
            return false
          end
          self.menuPage = nextPage
          return true
        end
      end

      local firstIndex = ((desiredPage - 1) * contentPerPage) + 1
      local lastIndex = math.min(entryCount, firstIndex + contentPerPage - 1)
      local pagedEntries = {
        {
          text = string.format("Page %d / %d", desiredPage, pageCount),
          isTitle = true,
          notCheckable = true,
        },
      }

      if desiredPage > 1 then
        pagedEntries[#pagedEntries + 1] = {
          text = "< Previous Page",
          notCheckable = true,
          keepShownOnClick = true,
          func = function()
            self.menuPage = desiredPage - 1
          end,
        }
      end

      for i = firstIndex, lastIndex do
        pagedEntries[#pagedEntries + 1] = entries[i]
      end

      if desiredPage < pageCount then
        pagedEntries[#pagedEntries + 1] = {
          text = "Next Page >",
          notCheckable = true,
          keepShownOnClick = true,
          func = function()
            self.menuPage = desiredPage + 1
          end,
        }
      end

      entries = pagedEntries
      entryCount = #entries
    else
      self.menuPage = 1
    end

    if entryCount < 1 then
      entries = {
        { text = "No entries", disabled = true, notCheckable = true },
      }
      entryCount = 1
    end

    popup:ClearAllPoints()
    popup:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2)
    popup:SetWidth(popupWidth)
    local searchHeight = searchEnabled and 28 or 0
    popup:SetHeight((entryCount * rowHeight) + 12 + searchHeight)

    if searchBox then
      if searchEnabled then
        searchBox:ClearAllPoints()
        searchBox:SetPoint("TOPLEFT", 6, -6)
        searchBox:SetPoint("TOPRIGHT", -6, -6)
        searchBox.hint:SetText(searchPlaceholder)
        root.__suppressSearchRefresh = true
        if tostring(searchBox:GetText() or "") ~= tostring(self.searchText or "") then
          searchBox:SetText(tostring(self.searchText or ""))
        end
        root.__suppressSearchRefresh = nil
        searchBox:Show()
      else
        searchBox:Hide()
      end
      UpdateSearchHint()
    end

    self.activeMenu = menu
    self:ApplyTheme()
    for i = 1, entryCount do
      local row = EnsureRow(i)
      row:ClearAllPoints()
      if i == 1 then
        row:SetPoint("TOPLEFT", 6, -(6 + searchHeight))
        row:SetPoint("TOPRIGHT", -6, -(6 + searchHeight))
      else
        row:SetPoint("TOPLEFT", rows[i - 1], "BOTTOMLEFT", 0, 0)
        row:SetPoint("TOPRIGHT", rows[i - 1], "BOTTOMRIGHT", 0, 0)
      end
      ApplyRowAppearance(row, entries[i])
    end
    for i = entryCount + 1, #rows do
      rows[i]:Hide()
      rows[i].entry = nil
    end

    return true
  end

  function root:CloseMenu()
    HidePopup()
  end

  function root:OpenMenu()
    if self.enabled == false then
      return false
    end
    if type(self.menuBuilder) ~= "function" then
      return false
    end
    if editor and editor.__lidcouncilOpenDropdown and editor.__lidcouncilOpenDropdown ~= self then
      editor.__lidcouncilOpenDropdown:CloseMenu()
    end
    if not self:RefreshMenu() then
      return false
    end
    if editor then
      editor.__lidcouncilOpenDropdown = self
    end
    popup:Show()
    if searchBox and searchBox:IsShown() then
      searchBox:SetFocus()
      searchBox:HighlightText()
      UpdateSearchHint()
    end
    button.arrow:SetText("^")
    return true
  end

  function root:ToggleMenu()
    if popup:IsShown() then
      self:CloseMenu()
      return
    end
    self:OpenMenu()
  end

  button:SetScript("OnClick", function()
    if root.enabled == false then
      return
    end
    root:ToggleMenu()
  end)
  root:SetScript("OnMouseUp", function(_, mouseButton)
    if mouseButton ~= "LeftButton" or root.enabled == false then
      return
    end
    if type(GetMouseFocus) == "function" and GetMouseFocus() ~= root then
      return
    end
    root:ToggleMenu()
  end)

  popup:SetScript("OnMouseWheel", function(_, delta)
    local menu = root.activeMenu
    if type(menu) ~= "table" or type(menu.onMouseWheel) ~= "function" then
      return
    end
    local changed = menu.onMouseWheel(delta)
    if changed then
      root:RefreshMenu()
    end
  end)

  searchBox:SetScript("OnEditFocusGained", UpdateSearchHint)
  searchBox:SetScript("OnEditFocusLost", UpdateSearchHint)
  searchBox:SetScript("OnMouseWheel", function(_, delta)
    local menu = root.activeMenu
    if type(menu) ~= "table" or type(menu.onMouseWheel) ~= "function" then
      return
    end
    local changed = menu.onMouseWheel(delta)
    if changed then
      root:RefreshMenu()
    end
  end)
  searchBox:SetScript("OnEscapePressed", function()
    root:CloseMenu()
  end)
  searchBox:SetScript("OnEnterPressed", function(self)
    self:ClearFocus()
    UpdateSearchHint()
  end)
  searchBox:SetScript("OnTextChanged", function(self)
    if root.__suppressSearchRefresh then
      return
    end
    root.searchText = tostring(self:GetText() or "")
    root.menuPage = 1
    UpdateSearchHint()
    if popup:IsShown() then
      root:RefreshMenu()
    end
  end)

  root.button = button
  root.SetDisplayText = root.SetText
  root:SetText(root.displayText)
  root:SetDropdownWidth(root.dropdownWidth)
  SyncButtonInteractivity()
  root:HookScript("OnShow", function()
    if button.ClearAllPoints then
      button:ClearAllPoints()
    end
    button:SetAllPoints(root)
    if button.Show then
      button:Show()
    end
    SyncButtonInteractivity()
    root:ApplyTheme()
  end)
  if editor then
    editor.__lidcouncilCustomDropdowns = editor.__lidcouncilCustomDropdowns or {}
    editor.__lidcouncilCustomDropdowns[#editor.__lidcouncilCustomDropdowns + 1] = root
  end
  root:ApplyTheme()
  root:HookScript("OnHide", function()
    root:CloseMenu()
  end)
  if editor and editor.HookScript then
    editor:HookScript("OnHide", function()
      if editor.__lidcouncilOpenDropdown == root then
        root:CloseMenu()
      end
    end)
  end

  return root
end

PublishApi("LIDCOUNCIL_CreateCustomDropdownControl", CreateCustomDropdownControl)
