local MAJOR = "LibDropdown-1.0"
local MINOR = 20240724

local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end

local assert = assert
local ipairs = ipairs
local math = math
local max = max
local min = min
local next = next
local pairs = pairs
local select = select
local table = table
local setmetatable = setmetatable
local tinsert = tinsert
local tostring = tostring
local tremove = tremove
local type     = type
local wipe = wipe

local CreateFrame = CreateFrame
local PlaySound = PlaySound
local ShowUIPanel = ShowUIPanel
local GetMouseFocus = GetMouseFocus
local UISpecialFrames = UISpecialFrames

local ChatFrame1 = ChatFrame1
local ColorPickerFrame = ColorPickerFrame
local GameTooltip = GameTooltip

local GameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor

local HIGHLIGHT_FONT_COLOR = HIGHLIGHT_FONT_COLOR
local NORMAL_FONT_COLOR = NORMAL_FONT_COLOR

local framePool = lib.framePool or {}
lib.framePool = framePool

local buttonPool = lib.buttonPool or {}
lib.buttonPool = buttonPool

local inputPool = lib.inputPool or {}
lib.inputPool = inputPool

local frameHideQueue = lib.frameHideQueue or {}
lib.frameHideQueue = frameHideQueue

local sliderPool = lib.sliderPool or {}
lib.sliderPool = sliderPool

local AddButton, Refresh, GetRoot, SetChecked, HideRecursive, ShowGroup, HideGroup, SetGroup, NewDropdownFrame, NewDropdownButton, ReleaseFrame, AcquireButton, ReleaseButton, AcquireFrame
local UIParent = _G.UIParent

local openMenu

local noop = lib.noop or function() end
lib.noop = noop
local new, del = lib.new, lib.del
if not lib.new then
    local list = setmetatable({}, {__mode='k'})
    function new(...)
        local t = next(list)
        if t then
            list[t] = nil
            for i = 1, select('#', ...) do
                t[i] = select(i, ...)
            end
            return t
        else
            return {...}
        end
    end
    function del(t)
        setmetatable(t, nil)
        for k in pairs(t) do
            t[k] = nil
        end
        t[''] = true
        t[''] = nil
        list[t] = true
        return nil
    end
    lib.new, lib.del = new, del
end
new, del = lib.new, lib.del

-- Make the frame match the tooltip
local function InitializeFrame(frame)
    if TooltipBackdropTemplateMixin then
        frame.layoutType = GameTooltip.layoutType
        if GameTooltip.layoutType then
            frame.NineSlice:SetCenterColor(GameTooltip.NineSlice:GetCenterColor())
            frame.NineSlice:SetBorderColor(GameTooltip.NineSlice:GetBorderColor())
        end
    else
        local backdrop = GameTooltip:GetBackdrop()

        frame:SetBackdrop(backdrop)

        if backdrop then
            frame:SetBackdropColor(GameTooltip:GetBackdropColor())
            frame:SetBackdropBorderColor(GameTooltip:GetBackdropBorderColor())
        end
    end
    frame:SetScale(GameTooltip:GetScale())
    if (frame:GetEffectiveScale() ~= GameTooltip:GetEffectiveScale()) then -- consider applied SetIgnoreParentScale() on GameTooltip regarding scaling of the frame
        frame:SetScale(frame:GetScale() * GameTooltip:GetEffectiveScale() / frame:GetEffectiveScale())
    end
end

local editBoxCount = 1
local function AcquireInput()
    local frame = tremove(inputPool)

    if frame then
        frame.released = false
        return frame
    end

    frame = CreateFrame("EditBox", "LibDropDownEditBox"..editBoxCount, UIParent, "InputBoxTemplate")
    frame:SetAutoFocus(false)
    editBoxCount = editBoxCount + 1
    frame:SetScript("OnEscapePressed", function(self)
        self:ClearFocus()
        self:GetParent():GetRoot():Refresh()
    end)

    frame:SetScript("OnEnterPressed", function(self)
        if self.ValueChanged then
            self:ValueChanged(self:GetText())
        end
    end)
    frame.refresh = noop
    return frame
end

local function AcquireSlider()
    local frame = tremove(sliderPool)
    if frame then
        frame.released = false
        return frame
    end

    local frame = CreateFrame("Slider", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
    frame:SetWidth(10)
    frame:SetHeight(150)
    frame:SetOrientation("VERTICAL")
    frame:SetBackdrop({
        bgFile = [[Interface\Buttons\UI-SliderBar-Background]],
        edgeFile = [[Interface\Buttons\UI-SliderBar-Border]],
        tile = true,
        tileSize = 8,
        edgeSize = 8,
        insets = {left = 3, right = 3, top = 6, bottom = 6}
    })
    frame:SetThumbTexture([[Interface\Buttons\UI-SliderBar-Button-Vertical]])
    frame:EnableMouseWheel()
    frame:Show()

    local text = frame:CreateFontString(nil, nil, "GameFontNormalSmall")
    text:SetPoint("TOP", frame, "BOTTOM")
    text:SetTextColor(1, 1, 1, 1)
    frame.text = text

    frame:SetScript("OnMouseWheel", function(self, direction, ...)
        if not direction then return end -- huh?
        local mn, mx = self:GetMinMaxValues()
        local nv = min(mx, max(mn, self:GetValue() + ((self.step or 1) * direction * -1)))
        self:SetValue(nv)
    end)

    frame:SetScript("OnValueChanged", function(self)
        local mn, mx = self:GetMinMaxValues()
        local nv = min(mx, max(mn, self:GetValue()))
        if nv ~= self:GetValue() then
            self:SetValue(nv)
            return
        end
        local n, x = self:GetMinMaxValues()
        local ev = x - nv
        frame.text:SetText(ev)

        if self.ValueChanged then
            self:ValueChanged(self:GetValue())
        end
    end)
    frame.refresh = noop
    return frame
end

local function ReleaseSlider(slider)
    if slider.released then return end
    slider.released = true
    slider:Hide()
    slider:SetParent(UIParent)
    tinsert(sliderPool, slider)
    return nil
end

local function ReleaseInput(input)
    if input.released then return end
    input.released = true
    input:Hide()
    input:SetParent(UIParent)
    tinsert(inputPool, input)
    return nil
end

local function MouseOver(frame)
    if GetMouseFocus then
        local f = GetMouseFocus()
        while f and f ~= UIParent do
            if f == frame then return true end
            f = f:GetParent()
        end
    else
        local mouseFoci = GetMouseFoci()
        for _, f in ipairs(mouseFoci) do
            while f do
                if f == frame then
                    return true
                end
                f = f:GetParent()
            end
        end
    end
    return false
end
-- Frame methods
function AddButton(self, b)
    b:ClearAllPoints()
    b:SetParent(self)
    b:Show()
    if #self.buttons == 0 then
        b:SetPoint("TOPLEFT", self, "TOPLEFT", 0, -4)
        b:SetPoint("TOPRIGHT", self, "TOPRIGHT", 0, -4)
    else
        b:SetPoint("TOPLEFT", self.buttons[#self.buttons], "BOTTOMLEFT")
        b:SetPoint("TOPRIGHT", self.buttons[#self.buttons], "BOTTOMRIGHT")
    end
    tinsert(self.buttons, b)
    self:SetHeight(#self.buttons * b:GetHeight() + 8)
end

function Refresh(self)
    if not self:IsVisible() then return end
    local maxWidth = 1
    for i = 1, #self.buttons do
        self.buttons[i]:refresh()
        maxWidth = math.max(maxWidth, self.buttons[i].text:GetStringWidth() + 60)
    end
    self:SetWidth(maxWidth)
end

function GetRoot(self)
    local parent = self:GetParent()
    if parent and parent.GetRoot then
        return parent:GetRoot()
    else
        return self
    end
end

-- Button methods
function ShowGroup(self)
    if not self.enabled then return end
    if not self.groupFrame then
        self.groupFrame = self:AcquireFrame()
    end
    if not self.handled then
        self.handler(lib, self.group, self.groupFrame)
        self.handled = true
    end
    self.groupFrame:SetPoint("TOPLEFT", self, "TOPRIGHT", 0, 4)
    self.groupFrame:Show()
    self.groupFrame:Refresh()
    self:LockHighlight()
    self:enter()
    if self.groupFrame.Showing then
        self.groupFrame:Showing()
    end
end

function HideGroup(self)
    if not self then return end
    if MouseOver(self) then return end
    if self.groupFrame then
        self.groupFrame:Hide()
    end
    self:UnlockHighlight()
    self:leave()
    if self.groupFrame.Hiding then
        self.groupFrame:Hiding()
    end
end

function SetChecked(self, val)
    self.checked = val
    if val then
        self.check:Show()
        self.check:SetDesaturated(false)
    elseif val == false or not self.tristate then
        self.check:Hide()
    elseif val == nil then
        self.check:Show()
        self.check:SetDesaturated(true)
    end
end

function HideRecursive(self)
    HideGroup(self:GetParent())
end

function SetGroup(self, t, handler)
    if t then
        self.expand:Show()
        self:SetScript("OnEnter", ShowGroup)
        self:SetScript("OnLeave", HideGroup)
        self.group = t
        self.handler = handler
    else
        self.expand:Hide()
        self:SetScript("OnEnter", nil)
        self:SetScript("OnLeave", nil)
        self.group = nil
        self.handler = nil
    end
    self.clickable = t == nil
end

-- Pool methods
local frameCount = 0
function NewDropdownFrame()
    local template = (TooltipBackdropTemplateMixin and "TooltipBackdropTemplate") or (BackdropTemplateMixin and "BackdropTemplate")

    local frame = CreateFrame("Frame", "LibDropdownFrame" .. frameCount, UIParent, template)
    frameCount = frameCount + 1
    frame:SetPoint("CENTER", UIParent, "CENTER")
    frame:SetWidth(10)
    frame:SetHeight(24)
    frame:EnableMouse(true)
    frame.AddButton = AddButton
    frame.Refresh = Refresh
    frame.GetRoot = GetRoot
    frame.isDropdownFrame = true
    frame.Release = ReleaseFrame
    frame.AcquireButton = AcquireButton
    -- make it close on escape
    tinsert(UISpecialFrames, frame:GetName())
    return frame
end

do
    local function enterButton(self)
        GameTooltip_SetDefaultAnchor(GameTooltip, self)
        GameTooltip:ClearLines()
        GameTooltip:AddLine(self.text:GetText())
        if self.desc then
            GameTooltip:AddLine("|cffffffff" .. self.desc .. "|r")
        end
        GameTooltip:Show()
    end

    local function leaveButton(self)
        GameTooltip:Hide()
        local p = self:GetParent()
        if p then
            local f = p:GetScript("OnLeave")
            if f then
                f(p)
            end
        end
    end

    local function pushText(self)
        if not self.clickable then return end
        self.text:SetPoint("TOP", self, "TOP", 0, -2)
        self.text:SetPoint("LEFT", self.check, "RIGHT", 6, 0)
    end

    local function unpushText(self)
        if not self.clickable then return end
        self.text:SetPoint("TOP", self, "TOP", 0, 0)
        self.text:SetPoint("LEFT", self.check, "RIGHT", 4, 0)
    end

    local function click(self)
        if self.OnClick and self.clickable then
            self.OnClick(self)
            PlaySound(856)
            if self:IsShown() then
                self:GetParent():GetRoot():Refresh()
            end
        end
    end

    local function settext(self, t)
        self.text:SetText(t)
    end

    local function disable(self)
        self.enabled = false
        self:SetScript("OnMouseDown", nil)
        self:SetScript("OnMouseUp", nil)
        self.text:SetTextColor(0.5, 0.5, 0.5, 1)
        self.check:SetDesaturated(true)
        self.expand:SetDesaturated(true)
        self:oldDisable()
    end

    local function enable(self)
        self.enabled = true
        self:SetScript("OnMouseDown", pushText)
        self:SetScript("OnMouseUp", unpushText)
        self.text:SetTextColor(1, 1, 1, 1)
        -- self.text:SetTextColor(self:GetTextColor()) -- Removed in 3.0
        self.check:SetDesaturated(false)
        self.expand:SetDesaturated(false)
        self:oldEnable()
    end

    local function revert()
        --ColorPickerFrame.previousValues.frame
    end

    local function setColor()
        local r, g, b = ColorPickerFrame:GetColorRGB()
        local a = ColorPickerFrame.opacity or 1
        local f = ColorPickerFrame.previousValues.frame
        f:GetNormalTexture():SetVertexColor(r, g, b, a)
        if f:GetParent().OnClick then
            f:GetParent():OnClick(r, g, b, a)
        end
    end

    local function revert()
        local p = ColorPickerFrame.previousValues
        ColorPickerFrame:SetColorRGB(p.r, p.g, p.b)
        ColorPickerFrame.opacity = p.opacity
        setColor()
    end

    local function openColorPicker(self)
        local p = self:GetParent()
        p.r, p.g, p.b, p.a = self:GetNormalTexture():GetVertexColor()

        ColorPickerFrame.hasOpacity = p.hasOpacity
        ColorPickerFrame.opacityFunc = p.opacityFunc
        ColorPickerFrame.opacity = p.a
        ColorPickerFrame:SetColorRGB(p.r, p.g, p.b)
        ColorPickerFrame.previousValues = ColorPickerFrame.previousValues or {}
        ColorPickerFrame.previousValues.r = p.r
        ColorPickerFrame.previousValues.g = p.g
        ColorPickerFrame.previousValues.b = p.b
        ColorPickerFrame.previousValues.opacity = p.a
        ColorPickerFrame.previousValues.frame = self
        ColorPickerFrame.cancelFunc = revert
        ColorPickerFrame.func = setColor

        ShowUIPanel(ColorPickerFrame)
    end

    local function highlightSwatch(self)
        self.tex:SetTexture(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
    end

    local function unhighlightSwatch(self)
        self.tex:SetTexture(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
    end

    local function makeTitle(self, t)
        -- self.text:SetJustifyH("CENTER")
        self.text:ClearAllPoints()
        self.text:SetPoint("LEFT", self, "LEFT", 16, 0)
        self.text:SetPoint("RIGHT", self, "RIGHT", -16, 0)
        self.text:SetTextColor(1, 0.8, 0, 1)
        self.clickable = false
        if t then self.text:SetText(t) end
    end

    local function makeButton(self, t)
        local text, frame, check, expand = self.text, self, self.check, self.expand
        text:ClearAllPoints()
        text:SetPoint("TOP", frame, "TOP")
        text:SetPoint("LEFT", check, "RIGHT", 4, 0)
        text:SetPoint("BOTTOM", frame, "BOTTOM")
        text:SetPoint("RIGHT", expand, "LEFT", -4, 0)
        text:SetJustifyH("LEFT")
        self.text:SetTextColor(1, 1, 1, 1)
        self.clickable = true
        if t then text:SetText(t) end
    end

    function NewDropdownButton(f)
        local frame = f or CreateFrame("Button", nil, UIParent)
        frame:SetHighlightTexture([[Interface\QuestFrame\UI-QuestTitleHighlight]])
        frame:GetHighlightTexture():SetBlendMode("ADD")
        -- frame:SetDisabledTextColor(0.5, 0.5, 0.5, 1) -- Removed in 3.0
        frame:SetPushedTextOffset(3,-3)
        frame:SetHeight(18)

        local check = frame.check or frame:CreateTexture()
        check:SetTexture([[Interface\Buttons\UI-CheckBox-Check]])
        check:SetWidth(18)
        check:SetHeight(18)
        check:SetPoint("LEFT", frame, "LEFT", 4, 0)
        check:Hide()
        frame.check = check

        local expand = frame.expand or frame:CreateTexture()
        expand:SetTexture([[Interface\ChatFrame\ChatFrameExpandArrow]])
        expand:SetWidth(16)
        expand:SetHeight(16)
        expand:SetPoint("RIGHT", frame, "RIGHT", -4, 0)
        expand:Hide()
        frame.expand = expand

        local text = frame.text or frame:CreateFontString(nil, nil, "GameFontHighlightSmall")
        frame.text = text
        frame.SetText = settext
        frame.MakeButton = makeButton
        frame.MakeTitle = makeTitle
        frame:MakeButton("<not set>")

        local swatch = frame.swatch or CreateFrame("Button", nil, frame)
        swatch:SetWidth(18)
        swatch:SetHeight(18)
        swatch.tex = swatch.text or swatch:CreateTexture(nil, "BACKGROUND")
        swatch.tex:SetPoint("CENTER", swatch, "CENTER")
        swatch.tex:SetWidth(swatch:GetWidth()-2)
        swatch.tex:SetHeight(swatch:GetHeight()-2)
        -- swatch.tex:SetTexture([[Interface\ChatFrame\ChatFrameColorSwatch]])
        swatch:SetNormalTexture([[Interface\ChatFrame\ChatFrameColorSwatch]])
        swatch:SetPoint("RIGHT", frame, "RIGHT", -4, 0)
        swatch:SetScript("OnClick", openColorPicker)
        swatch:SetFrameLevel(frame:GetFrameLevel() + 100)
        swatch:SetScript("OnEnter", highlightSwatch)
        swatch:SetScript("OnLeave", unhighlightSwatch)
        swatch:Hide()
        frame.swatch = swatch
        unhighlightSwatch(swatch)

        frame:SetScript("OnEnter", enterButton)
        frame:SetScript("OnLeave", leaveButton)
        frame.enter, frame.leave = enterButton, leaveButton

        frame:SetScript("OnMouseDown", pushText)
        frame:SetScript("OnMouseUp", unpushText)
        frame:SetScript("OnClick", click)
        frame.SetGroup = SetGroup
        frame.SetChecked = SetChecked
        frame.AcquireFrame = AcquireFrame
        frame.GetRoot = GetRoot
        frame.oldDisable, frame.Disable = frame.Disable, disable
        frame.oldEnable, frame.Enable = frame.Enable, enable
        frame.Release = ReleaseButton

        frame.clickable = true
        frame.enabled = true
        frame:Enable()

        return frame
    end
end

function ReleaseFrame(f)
    if f.rootMenu then
        openMenu = nil
    end
    if f.released then return end
    f.released = true
    f.data = nil
    f.dataname = nil
    f.rootMenu = nil

    f:Hide()
    f:SetParent(UIParent)
    f:ClearAllPoints()

    tinsert(framePool, f)
    for i = 1, #f.buttons do
        local button = tremove(f.buttons)
        button:Release()
    end
    return nil
end

function AcquireButton(p)
    local b = NewDropdownButton(tremove(buttonPool))
    b.released = false
    b:EnableMouse(true)
    b:Show()
    p:AddButton(b)
    return b
end

function ReleaseButton(b)
    if b.released then return end
    tinsert(buttonPool, b)
    b.desc = nil
    b.released = true
    b.Enable = b.oldEnable
    b.Disable = b.oldDisable
    b.handled = false
    b:SetParent(UIParent)
    b:Hide()
    if b.slider then
        b.slider = ReleaseSlider(b.slider)
    end
    if b.input then
        b.input = ReleaseInput(b.input)
    end
    b:ClearAllPoints()
    b:MakeButton("(released)")
    if b.groupFrame then
        b.groupFrame.Showing = nil
        b.groupFrame = b.groupFrame:Release()
    end

end

local function frameReleaseOnHide(self)
    self:SetScript("OnHide", nil)
    self:Release()
end

function AcquireFrame(parent, toplevel)
    local f = tremove(framePool) or NewDropdownFrame()
    InitializeFrame(f) -- set the look of the frame
    f.released = false
    f.buttons = f.buttons or {}
    f:SetParent(parent or UIParent)
    if parent then
        f:SetScript("OnLeave", HideRecursive)
    else
        f:SetScript("OnLeave", nil)
    end
    if toplevel then
        f:SetScript("OnHide", frameReleaseOnHide)
    else
        f:SetScript("OnHide", nil)
    end
    f:ClearAllPoints()
    f:Show()
    return f
end

----------------------------------------------------------------------
----------------------------------------------------------------------
do
    local Ace3 = {}
    local grefresh
    local info = {}
    local options
    local currentOptionData
    local function setup(k, v, parent)
        local b = parent:AcquireButton()
        b.data = v
        b.option = v
        b.dataname = k
        b.refresh = grefresh
        return b
    end

    local function setInfoOptions()
        local option = options
        for _, key in ipairs(info) do
            if option.args and option.args[key] then
                option = option.args[key]
            else
                return
            end
        end
        info.option = option
        info.type = option.type
    end

    local function initInfo(type)
        info.options = options
        info.appName = options.name
        info.type = type
        info.uiType = "dropdown"
        info.uiName = "LibDropdown-1.0"
    end

    local function wipeInfo()
        local type = info.type
        wipe(info)
        initInfo(type)
    end

    local function runHandler(button, handler, ...)
        info.handler = handler
        info.option = button.data
        if not button.rootMenu then
            tinsert(info, 1, button.dataname)
        end
        local v = button.data
        if v and v[handler] then
            local ht = type(v[handler])
            if ht == "function" then
                setInfoOptions()
                local ret, r1, r2, r3 = v[handler](info, ...)
                wipeInfo()
                return ret, r1, r2, r3
            elseif ht == "table" then
                return v[handler]
            elseif ht == "string" then
                local t = runHandler(button, "handler", ...)
                if type(t) == "table" then
                    setInfoOptions()
                    local ret, r1, r2, r3 = t[v[handler]](t, info, ...)
                    wipeInfo()
                    return ret, r1, r2, r3
                end
            end
        elseif v and v[handler] == false then
            return nil -- Is this right?
        else
            if button.GetParent then
                local pp = button:GetParent() and button:GetParent():GetParent()
                if not pp or not pp.data then
                    pp = button:GetParent()
                end
                if pp and pp.data then
                    return runHandler(pp, handler, ...)
                end
            end
        end
        wipeInfo()
        return nil
    end

    function grefresh(self)
        local isDisabled = false
        self:SetText(self.data.name)
        self.desc = self.data.desc
        if type(self.data.disabled) == "function" then
            if self.data.disabled() then
                self:Disable()
                isDisabled = true
            else
                self:Enable()
            end
        elseif type(self.data.disabled) == "boolean" then
            if self.data.disabled then
                self:Disable()
                isDisabled = true
            else
                self:Enable()
            end
        end
        return isDisabled
    end

    -- group
    do
        local function refresh(self)
            grefresh(self)
            if not self.groupFrame or self.groupFrame == self:GetParent() then return end
            self.groupFrame:Refresh()
        end
        function Ace3.group(k, v, parent)
            local b = setup(k, v, parent)
            if v.inline then
                -- TODO: Add heading
                local b2 = parent:AcquireButton()
                b:MakeTitle(k)
                b2.refresh = noop
                lib:OpenAce3Menu(v.args, parent)
            else
                b:SetGroup(v.args, lib.OpenAce3Menu)
            end
            b.refresh = refresh
        end
    end

    -- execute
    function Ace3.execute(k, v, parent)
        local b = setup(k, v, parent)
        b:SetText(v.name)
        b.desc = v.desc
        b.OnClick = function(self)
            initInfo('execute')
            runHandler(self, "func")
            if self:IsShown() then
                self:GetRoot():Refresh()
            end
        end
    end

    -- input
    do
        local function refresh(self)
            grefresh(self)
            self.input:SetText(runHandler(self, "get") or "")
        end

        local function inputValueChanged(self, val)
            initInfo('input')
            runHandler(self:GetParent():GetParent(), "set", val)
            if self:IsShown() then
                self:GetParent():GetRoot():Refresh()
            end
        end

        function Ace3.input(k, v, parent)
            local b = setup(k, v, parent)
            b:SetGroup(v, lib.Ace3InputShow)
            b.input = AcquireInput()
            b.refresh = refresh
        end

        local function showInput(frame)
            local data = frame.data
            local input = frame:GetParent().input
            input:SetParent(frame)
            input:ClearAllPoints()
            input:SetPoint("LEFT", frame, "LEFT", 10, 0)
            frame:SetWidth(185)
            input:SetWidth(170)
            input:SetHeight(34)
            frame:SetHeight(34)
            input.ValueChanged = inputValueChanged
            input:Show()
            refresh(frame:GetParent())
        end

        function lib:Ace3InputShow(t, parent)
            parent.data = t
            parent.Showing = showInput
        end
    end

    -- toggle
    do
        local function refresh(self)
            local disabled = grefresh(self)
            initInfo('toggle')
            local actual = runHandler(self, "get")
            if disabled then
                self:SetChecked(false)
            else
                self:SetChecked(actual)
            end
        end
        local function onClick(self)
            initInfo('toggle')
            if self.data.tristate then
                local val = runHandler(self, "get")
                local sv
                if val == nil then sv = true
                elseif val == true then sv = false
                else sv = nil end
                runHandler(self, "set", sv)
            else
                local val = not runHandler(self, "get")
                runHandler(self, "set", val)
            end

            if self:IsShown() then
                self:GetRoot():Refresh()
            end
        end

        function Ace3.toggle(k, v, parent)
            local b = setup(k, v, parent)
            b.OnClick = onClick
            b.tristate = v.tristate
            b.refresh = refresh
        end
    end

    -- header
    do
        local function refresh(self)
            grefresh(self)
        end

        function Ace3.header(k, v, parent)
            local b = setup(k, v, parent)
            b:MakeTitle(k)
            b:EnableMouse(false)
            b.tristate = nil
            b.refresh = refresh
        end
    end

    -- color
    do
        local function refresh(self)
            grefresh(self)
            initInfo('color')
            self.swatch:GetNormalTexture():SetVertexColor(runHandler(self, "get"))
        end
        function Ace3.color(k, v, parent)
            local b = setup(k, v, parent)
            b.swatch:Show()
            b.clickable = false
             b.refresh = refresh
            b.OnClick = function(self, r, g, b, a)
                runHandler(self, "set", r, g, b, a)
                self:GetRoot():Refresh()
            end
        end
    end

    -- select
    do
        local function refresh(self)
            grefresh(self)
            if self.groupFrame then
                self.groupFrame:Refresh()
            end
        end
        function Ace3.select(k, v, parent)
            local b = setup(k, v, parent)
            b.parentTree = v
            b:SetGroup(v.values, lib.Ace3MenuSelect)
            b.refresh = refresh
        end

        local function buttonRefresh(self)
            initInfo('select')
            self:SetChecked( runHandler(self:GetParent():GetParent(), "get") == self.value )
        end
        function lib:Ace3MenuSelect(t, parent)
            initInfo('select')
            if type(t) == "string" or type(t) == "function" then
                t = runHandler(parent, "values")
            end
            if type(t) == "table" then
                for k, v in pairs(t) do
                    local b = parent:AcquireButton()
                    b:SetText(v)
                    b.value = k
                    b.OnClick = function(self)
                        initInfo('select')
                        runHandler(self:GetParent():GetParent(), "set", self.value)
                        self:GetParent():GetRoot():Refresh()
                    end
                    b.refresh = buttonRefresh
                end
            end
        end
    end

    -- range
    -- Some extra tricksery for mousewheel on the containing frame. A bit more user friendly.
    do
        local function refresh(self)
            grefresh(self)
            initInfo('range')
            self.slider:SetValue(runHandler(self, "get") or self.slider:GetMinMaxValues())
            initInfo('range')
            self.slider.text:SetText(runHandler(self, "get"))
        end

        function Ace3.range(k, v, parent)
            local b = setup(k, v, parent)
            b:SetGroup(v, lib.Ace3SliderShow)
            b.slider = AcquireSlider()
            b.refresh = refresh
        end

        local function onWheel(f)
            f:GetParent().slider:GetScript("OnMouseWheel")(f:GetParent().slider)
        end

        local function removeMousewheelFuncs(f)
            tremove(f.buttons)
            f:EnableMouseWheel(false)
            f:SetScript("OnMouseWheel", nil)
            f.Hiding = nil
        end

        local function sliderValueChanged(self, val)
            initInfo('range')
            runHandler(self:GetParent():GetParent(), "set", val)
            self:GetParent():GetRoot():Refresh()
        end

        local function showSlider(frame)
            local data = frame.data
            local slider = frame:GetParent().slider

            slider:SetParent(frame)
            slider:ClearAllPoints()
            slider:SetPoint("CENTER", frame, "CENTER")
            slider:SetPoint("TOP", frame, "TOP", 0, -8)
            slider:SetHeight(150)
            slider.text:ClearAllPoints()
            slider.text:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 8)
            slider.text:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 8)

            slider.text:SetText(data.max)
            frame:SetWidth(max(60, slider.text:GetStringWidth() + 10))

            slider.step = data.bigStep
            slider:SetMinMaxValues(data.min or 0, data.max or 100)
            slider:SetValueStep(data.bigStep or data.step or 1)
            slider.ValueChanged = sliderValueChanged

            frame:EnableMouseWheel(true)
            frame:SetScript("OnMouseWheel", onWheel)
            frame.Hiding = removeMousewheelFuncs
            frame:SetHeight(180)

            slider:Show()
            refresh(frame:GetParent())
        end

        function lib:Ace3SliderShow(t, parent)
            parent.data = t
            parent.Showing = showSlider
        end
    end

    do
        local sortOptions = function(a, b)
        if (b.order or 100) > (a.order or 100) then return true
        elseif (b.order or 100) < (a.order or 100) then return false
        elseif b.name:lower() > a.name:lower() then return true
        else return false
        end
    end

        function lib:OpenAce3Menu(t, parent)
            assert(t and type(t) == "table", "Expected table, got "..type(t))
            if parent == nil and t.args then
                if openMenu then
                    openMenu:Release()
                end
                options = t
                openMenu = AcquireFrame(nil, true)
                openMenu:Show()
                openMenu.data = t
                openMenu.dataname = "Root menu"
                openMenu.rootMenu = true
                openMenu:SetPoint("CENTER", UIParent, "CENTER")
                self:OpenAce3Menu(t.args, openMenu)
                openMenu:Refresh()
                openMenu:SetFrameStrata("TOOLTIP")
                return openMenu
            else
                local sortedOpts = new()
                local lookup = new()
                for i = 1, #sortedOpts do
                    tremove(sortedOpts)
                end
                for k, v in pairs(t) do
                    lookup[v] = k
                    tinsert(sortedOpts, v)
                end
                table.sort(sortedOpts, sortOptions)
                for _, v in ipairs(sortedOpts) do
                    if Ace3[v.type] and not v.dropdownHidden and not v.hidden then
                        Ace3[v.type](lookup[v], v, parent)
                    end
                end
                sortedOpts = del(sortedOpts)
                lookup = del(lookup)
            end
        end
    end
end

------------------------------------------------------
------------------------------------------------------

local toggled = true
local r, g, b, a = 1, 0, 1, 1
local options = {"foo", "bar", "foobar"}
local optIndex = 1
local rangeVal, rangeVal2 = 100, 10
local inherits = {["inherittoggle"] = true}

local t = {
    type = "group",
    name = "group",
    desc = "group",
    args = {
        foo = {
            type = "input",
            name = "text",
            desc = "text desc",
            get = function(info) return "texting!" end,
            set = function(info, v) end
        },
        inherit = {
            type = "group",
            name = "inheritance test",
            desc = "inheritance test",
            get = function(info) ChatFrame1:AddMessage(("Got getter, getting %s (%s)"):format(tostring(info[#info]), tostring(inherits[info[#info]]))) return inherits[info[#info]] end,
            set = function(info, v) ChatFrame1:AddMessage("Got setter:" .. tostring(v)) inherits[info[#info]] = v end,
            args = {
                inherittoggle = {
                    type = "toggle",
                    name = "inherit toggle",
                    desc = "inherit toggle"
                },
            }
        },
        exec = {
            type = "execute",
            name = "Say hi",
            desc = "Execute, says hi",
            func = function() ChatFrame1:AddMessage("Hi!") end
        },
        range = {
            type = "range",
            name = "Range slider",
            desc = "Range slider",
            min = 0,
            max = 800,
            bigStep = 50,
            get = function(info) return rangeVal end,
            set = function(info, v) rangeVal = v end,
        },
        range2 = {
            type = "range",
            name = "Range slider 2",
            desc = "Range slider 2",
            min = 0,
            max = 80,
            bigStep = 5,
            get = function(info) return rangeVal2 end,
            set = function(info, v) rangeVal2 = v end,
        },
        toggle = {
            type = "toggle",
            name = "Toggle",
            desc = "Toggle",
            get = function() return toggled end,
            set = function(info, v) toggled = v end,
            disabled = function(info) return not toggled end
        },
        toggle3 = {
            type = "toggle",
            name = "Tristate Toggle Tristate Toggle Tristate Toggle",
            desc = "Tristate Toggle",
            tristate = true,
            get = function() return toggled end,
            set = function(info, v) toggled = v end
        },
        select = {
            type = "select",
            name = "select",
            desc = "select desc",
            values = options,
            get = function(info) return optIndex end,
            set = function(info, v) optIndex = v end
        },
        color = {
            type = "color",
            name = "color swatch",
            desc = "color swatch desc",
            get = function(info) return r, g, b, a end,
            set = function(info, _r, _g, _b, _a) r, g, b, a = _r, _g, _b, _a end
        },
        foo3 = {
            type = "group",
            name = "group!",
            desc = "group desc",
            args = {
                toggle3 = {
                    type = "toggle",
                    name = "Tristate Toggle Tristate Toggle Tristate Toggle",
                    desc = "Tristate Toggle",
                    tristate = true,
                    get = function() return toggled end,
                    set = function(info, v) toggled = v end
                },
            }
        },
        foo4 = {
            type = "group",
            name = "inline group!",
            desc = "inline group desc",
            inline = "true",
            args = {
                foo2 = {
                    type = "input",
                    name = "text3",
                    desc = "text3 desc",
                    get = function(info) return "texting!" end,
                    set = function(info, v) end
                }
            },
            order = 500
        },
        close = {
            type = "execute",
            name = "Close",
            desc = "Close this menu",
            func = function(self) end,
            order = 1000
        }
    }
}

--[[function testlibdropdown()
    LibStub("LibDropdown-1.0"):OpenAce3Menu(t)
end]]
if UIDropDownMenu_HandleGlobalMouseEvent then
    hooksecurefunc("UIDropDownMenu_HandleGlobalMouseEvent", function(button, event)
        if openMenu and event == "GLOBAL_MOUSE_DOWN" and (button == "LeftButton" or button == "RightButton") then
            for i = 0, frameCount - 1 do
                if _G["LibDropdownFrame" .. i]:IsMouseOver() then return end
            end

            openMenu:Release()
        end
    end)
else
    lib.mousecallback = EventRegistry:RegisterFrameEventAndCallback("GLOBAL_MOUSE_DOWN",function(ownerID,button)
        if openMenu and (button == "LeftButton" or button == "RightButton") then
            for i = 0, frameCount - 1 do
                if _G["LibDropdownFrame" .. i]:IsMouseOver() then return end
            end

            openMenu:Release()
        end
    end)
end
