local addonName, LRH = ...
LRH = LRH or {}
LRH.UI = LRH.UI or {}
LRH.UI.Dropdown = LRH.UI.Dropdown or {}

-- Simple "ElvUI-like" dropdown: custom list with scrolling, no UIDropDownMenu.
-- Provides: Create(parent, name, width, opts) -> dropdown object
-- opts: { maxVisible = 12 }

local DD = LRH.UI.Dropdown

local function SafeSetFont(fs, path, size, flags)
  if not (fs and fs.SetFont and path) then return end
  pcall(fs.SetFont, fs, path, size or 12, flags or "")
end

local function Clamp(v, lo, hi)
  if v < lo then return lo end
  if v > hi then return hi end
  return v
end

-- Measure text width using a neutral font to keep width stable even with font previews.
local function MeasureLongest(items, fontObj)
  local probe = LRH.__ddProbeFS
  if not probe then
    probe = UIParent:CreateFontString(nil, "ARTWORK", fontObj or "GameFontHighlightSmall")
    probe:Hide()
    LRH.__ddProbeFS = probe
  end
  if fontObj then probe:SetFontObject(fontObj) end
  local maxW = 0
  for _, it in ipairs(items or {}) do
    local t = (it and it.text) or ""
    probe:SetText(t)
    local w = probe:GetStringWidth() or 0
    if w > maxW then maxW = w end
  end
  return maxW
end

local function TruncateText(fs, maxW, text)
  if not fs then return text end
  fs:SetText(text or "")
  if (fs:GetStringWidth() or 0) <= maxW then return text end
  local t = text or ""
  local ell = "…"
  local lo, hi = 0, #t
  while lo < hi do
    local mid = math.floor((lo + hi) / 2)
    local cand = t:sub(1, mid) .. ell
    fs:SetText(cand)
    if (fs:GetStringWidth() or 0) <= maxW then
      lo = mid + 1
    else
      hi = mid
    end
  end
  local cut = math.max(0, lo - 1)
  local out = t:sub(1, cut) .. ell
  fs:SetText(out)
  return out
end

local function CreateBackdrop(frame)
  if not frame.SetBackdrop then return end
  frame:SetBackdrop({
    bgFile   = "Interface\\ChatFrame\\ChatFrameBackground",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tile = true, tileSize = 16, edgeSize = 14,
    insets = { left = 3, right = 3, top = 3, bottom = 3 }
  })
  frame:SetBackdropColor(0,0,0,0.85)
  frame:SetBackdropBorderColor(0.6,0.6,0.6,0.9)
end

local function EnsureListFrame(dd)
  if dd.listFrame then return dd.listFrame end

  local list = CreateFrame("Frame", nil, dd, "BackdropTemplate")
  list:SetFrameStrata("DIALOG")
  list:SetClampedToScreen(true)
  list:Hide()
  CreateBackdrop(list)

  list:SetPoint("TOPLEFT", dd, "BOTTOMLEFT", 0, -2)
  list:SetPoint("TOPRIGHT", dd, "BOTTOMRIGHT", 0, -2)

  list.maxVisible = dd.maxVisible or 12
  list.buttonH = 16
  list.pad = 6
  list.visible = list.maxVisible

  -- Layout constants (reserve space for checkmark so text never shifts)
  list.leftPad = 8
  list.checkW = 14
  list.textPad = list.leftPad + list.checkW + 4

  local height = list.pad*2 + list.visible*list.buttonH + 2
  list:SetHeight(height)

  local scroll = CreateFrame("ScrollFrame", nil, list, "FauxScrollFrameTemplate")
  scroll:SetPoint("TOPLEFT", list, "TOPLEFT", 0, -list.pad)
  scroll:SetPoint("BOTTOMRIGHT", list, "BOTTOMRIGHT", -26, list.pad)
  list.scroll = scroll

  list.buttons = {}
  for i=1, list.maxVisible do
    local b = CreateFrame("Button", nil, list)
    b:SetHeight(list.buttonH)
    b:SetPoint("LEFT", list, "LEFT", list.leftPad, 0)
    b:SetPoint("RIGHT", list, "RIGHT", -8, 0)
    if i == 1 then
      b:SetPoint("TOP", list, "TOP", 0, -list.pad)
    else
      b:SetPoint("TOP", list.buttons[i-1], "BOTTOM", 0, 0)
    end

    b.highlight = b:CreateTexture(nil, "HIGHLIGHT")
    b.highlight:SetAllPoints(true)
    b.highlight:SetColorTexture(1,1,1,0.08)

    b.check = b:CreateTexture(nil, "ARTWORK")
    b.check:SetSize(12,12)
    b.check:SetPoint("LEFT", b, "LEFT", 0, 0)
    b.check:Hide()

    b.text = b:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    b.text:SetPoint("LEFT", b, "LEFT", list.textPad, 0)
    b.text:SetPoint("RIGHT", b, "RIGHT", -4, 0)
    b.text:SetJustifyH("LEFT")

    list.buttons[i] = b
  end

  local function Update()
    local items = dd.items or {}
    local total = #items
    FauxScrollFrame_Update(scroll, total, list.visible, list.buttonH)

    local offset = FauxScrollFrame_GetOffset(scroll) or 0
    for i=1, list.maxVisible do
      local b = list.buttons[i]
      if i > (list.visible or list.maxVisible) then b:Hide() else
      local idx = offset + i
      local item = items[idx]
      if item then
        b:Show()
        b.text:SetText(item.text or "")
        -- preview font if provided
        if item.fontPath then
          SafeSetFont(b.text, item.fontPath, 12, "")
        else
          b.text:SetFontObject(GameFontHighlightSmall)
        end

        local isSel = (dd.selectedValue ~= nil and item.value == dd.selectedValue) or
                      (dd.selectedValue == nil and item.text == dd.displayText)
        if isSel then
          b.check:SetAtlas("checkmark-minimal", false)
          b.check:SetSize(12,12)
          b.check:Show()
        else
          b.check:Hide()
        end

        b:SetScript("OnClick", function()
          dd:SetSelectedValue(item.value, true)
          if dd.onSelect then
            dd.onSelect(item)
          end
          dd:Close()
        end)
      else
        b:Hide()
      end
      end
    end
  end

  scroll:SetScript("OnVerticalScroll", function(_, delta)
    FauxScrollFrame_OnVerticalScroll(_, delta, list.buttonH, Update)
  end)

  list.Update = Update
  dd.listFrame = list
  return list
end

local CloseAllDropdowns

local function EnsureClickCatcher()
  if LRH._ddClickCatcher then return LRH._ddClickCatcher end
  local c = CreateFrame("Frame", nil, UIParent)
  c:SetAllPoints(UIParent)
  c:SetFrameStrata("DIALOG")
  c:EnableMouse(true)
  c:Hide()
  c:SetScript("OnMouseDown", function()
    CloseAllDropdowns()
  end)
  LRH._ddClickCatcher = c
  return c
end

CloseAllDropdowns = function(except)
  if not LRH._openDropdowns then return end
  for dd,_ in pairs(LRH._openDropdowns) do
    if dd ~= except and dd.Close then dd:Close() end
  end
  if LRH._ddClickCatcher then LRH._ddClickCatcher:Hide() end
end

function DD:Create(parent, name, width, opts)
  opts = opts or {}
  local dd = CreateFrame("Button", name, parent, "BackdropTemplate")
  dd:SetSize(width or 140, 24)
  dd.minWidth = opts.minWidth
  dd.maxWidth = opts.maxWidth
  dd.maxVisible = opts.maxVisible or 14
  CreateBackdrop(dd)

  dd.text = dd:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
  dd.text:SetPoint("LEFT", dd, "LEFT", 10, 0)
  dd.text:SetPoint("RIGHT", dd, "RIGHT", -18, 0)
  dd.text:SetJustifyH("LEFT")
  dd.text:SetWordWrap(false)
  if dd.text.SetNonSpaceWrap then dd.text:SetNonSpaceWrap(false) end
  if dd.text.SetMaxLines then dd.text:SetMaxLines(1) end

  dd.arrow = dd:CreateTexture(nil, "ARTWORK")
  dd.arrow:SetSize(11,11)
  dd.arrow:SetPoint("RIGHT", dd, "RIGHT", -6, 0)
  dd.arrow:SetAtlas("NPE_ArrowDown", false)
  -- Crop atlas padding so the visible arrow scales more predictably
  dd.arrow:SetTexCoord(0.18, 0.82, 0.18, 0.82)

  dd.items = {}
  dd.selectedValue = nil
  dd.displayText = ""

  function dd:SetItems(items)
    self.items = items or {}
    -- refresh display text if selected value exists
    if self.selectedValue ~= nil then
      for _, it in ipairs(self.items) do
        if it.value == self.selectedValue then
          self:SetText(it.text)
          return
        end
      end
    end
  end

  function dd:SetText(t)
    self.displayText = t or ""
    local maxW = (self:GetWidth() or 140) - 40 -- left pad + arrow space
    TruncateText(self.text, maxW, self.displayText)
  end

  function dd:SetSelectedValue(v, updateText)
    self.selectedValue = v
    if updateText then
      for _, it in ipairs(self.items or {}) do
        if it.value == v then
          self:SetText(it.text)
          break
        end
      end
    end
  end

  function dd:GetSelectedValue()
    return self.selectedValue
  end

  function dd:Open()
    EnsureClickCatcher():Show()
    CloseAllDropdowns(self)
    local list = EnsureListFrame(self)

    local items = self.items or {}
    local total = #items
    local visible = math.min(total, list.maxVisible or self.maxVisible or 12)
    if visible < 1 then visible = 1 end
    list.visible = visible

    -- Dynamic height (no huge empty list if few entries)
    local height = list.pad*2 + visible*list.buttonH + 2
    list:SetHeight(height)

    -- Dynamic width based on longest label (+ room for check + padding + optional scrollbar)
    local textW = MeasureLongest(items, "GameFontHighlightSmall")
    local needScroll = (total > visible)
    local extra = 34 -- left reserved space + padding
    local scrollW = needScroll and 18 or 0
    local minW = self.minWidth or self:GetWidth()
    local maxW = self.maxWidth or 320
    local w = Clamp(textW + extra + scrollW, minW, maxW)
    list:SetWidth(w)

    -- If no scrollbar needed, give ScrollFrame full width
    if list.scroll then
      list.scroll:ClearAllPoints()
      list.scroll:SetPoint("TOPLEFT", list, "TOPLEFT", 0, -list.pad)
      if needScroll then
        list.scroll:SetPoint("BOTTOMRIGHT", list, "BOTTOMRIGHT", -26, list.pad)
      else
        list.scroll:SetPoint("BOTTOMRIGHT", list, "BOTTOMRIGHT", -6, list.pad)
      end
    end

    -- Open up if there's not enough space below
    list:ClearAllPoints()
    local uiBottom = 0
    local uiTop = UIParent:GetTop() or 0
    local ddBottom = self:GetBottom() or 0
    local ddTop = self:GetTop() or 0
    local spaceBelow = ddBottom - uiBottom
    local spaceAbove = uiTop - ddTop

    if spaceBelow < height + 20 and spaceAbove > spaceBelow then
      list:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 0, 2)
    else
      list:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)
    end

    list:Show()
    list:Update()

    LRH._openDropdowns = LRH._openDropdowns or {}
    LRH._openDropdowns[self] = true
  end

  function dd:Close()
    if self.listFrame then self.listFrame:Hide() end
    if LRH._openDropdowns then LRH._openDropdowns[self] = nil end
  end

  dd:SetScript("OnClick", function()
    if dd.listFrame and dd.listFrame:IsShown() then
      dd:Close()
    else
      dd:Open()
    end
  end)


  return dd
end