local addonName, FN = ...
FN = FN or FavoriteNeighborhood or {}
FavoriteNeighborhood = FN
FN.UI = FN.UI or {}
FN.UI.Dropdown = FN.UI.Dropdown or {}

local DD = FN.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

local function MeasureLongest(items, fontObj)
  local probe = FN.__ddProbeFS
  if not probe then
    probe = UIParent:CreateFontString(nil, "ARTWORK", fontObj or "GameFontHighlightSmall")
    probe:Hide()
    FN.__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 CloseAllDropdowns

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
  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 "")
          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 function EnsureClickCatcher()
  if FN._ddClickCatcher then return FN._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)
  FN._ddClickCatcher = c
  return c
end

CloseAllDropdowns = function(except)
  if not FN._openDropdowns then return end
  for dd,_ in pairs(FN._openDropdowns) do
    if dd ~= except and dd.Close then dd:Close() end
  end
  if FN._ddClickCatcher then FN._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)
  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 {}
    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
    else
      self:SetText(self.displayText or "")
    end
  end

  function dd:SetText(t)
    self.displayText = t or ""
    local maxW = (self:GetWidth() or 140) - 40
    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)
          return
        end
      end
      if v == nil then
        for _, it in ipairs(self.items or {}) do
          if it.value == nil then
            self:SetText(it.text)
            return
          end
        end
        self:SetText("")
      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

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

    local textW = MeasureLongest(items, "GameFontHighlightSmall")
    local needScroll = (total > visible)
    local extra = 34
    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 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

    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()

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

  function dd:Close()
    if self.listFrame then self.listFrame:Hide() end
    if FN._openDropdowns then FN._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
