local mouseUpRefCounts = {}
local currentOnMenuItemAddedCallback = nil
local currentOnMenuHiddenCallback = nil
local selectedIndex = nil
local lastCommandWasFromMenu = true

local DEFAULT_TEXT_COLOR = ZO_ColorDef:New(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_NORMAL))
local DEFAULT_TEXT_HIGHLIGHT = ZO_ColorDef:New(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_CONTEXT_HIGHLIGHT))

MENU_ADD_OPTION_LABEL = 1
MENU_ADD_OPTION_CHECKBOX = 2

MENU_TYPE_DEFAULT = 1
MENU_TYPE_COMBO_BOX = 2
MENU_TYPE_TEXT_ENTRY_DROP_DOWN = 3
MENU_TYPE_MULTISELECT_COMBO_BOX = 4

local menuInfo =
{
    [MENU_TYPE_DEFAULT] =
    {
        backdropEdge = "EsoUI/Art/Tooltips/UI-Border.dds",
        backdropCenter = "EsoUI/Art/Tooltips/UI-TooltipCenter.dds",
        backdropInsets = {16,16,-16,-16},
        backdropEdgeWidth = 128,
        backdropEdgeHeight = 16,
    },
    [MENU_TYPE_COMBO_BOX] =
    {
        backdropEdge = "EsoUI/Art/Miscellaneous/dropDown_edge.dds",
        backdropCenter = "EsoUI/Art/Miscellaneous/dropDown_center.dds",
        backdropInsets = {16, 16, -16, -16},
        backdropEdgeWidth = 128,
        backdropEdgeHeight = 16,
    },
    [MENU_TYPE_TEXT_ENTRY_DROP_DOWN] =
    {
        backdropEdge = "EsoUI/Art/Miscellaneous/textEntry_dropDown_edge.dds",
        backdropCenter = "EsoUI/Art/Miscellaneous/textEntry_dropDown_center.dds",
        backdropInsets = {8, 8, -8, -8},
        backdropEdgeWidth = 64,
        backdropEdgeHeight = 8,
        hideMunge = true,
    },
    [MENU_TYPE_MULTISELECT_COMBO_BOX] =
    {
        backdropEdge = "EsoUI/Art/Miscellaneous/dropDown_edge.dds",
        backdropCenter = "EsoUI/Art/Miscellaneous/dropDown_center.dds",
        backdropInsets = {16, 16, -16, -16},
        backdropEdgeWidth = 128,
        backdropEdgeHeight = 16,
    },
}

local function AnchorMenuToMouse(menuToAnchor)
    local x, y = GetUIMousePosition()
    local width, height = GuiRoot:GetDimensions()

    menuToAnchor:ClearAnchors()

    local right = true
    if x + ZO_Menu.width > width then
        right = false
    end
    local bottom = true
    if y + ZO_Menu.height > height then
        bottom = false
    end

    if right then
        if bottom then
            menuToAnchor:SetAnchor(TOPLEFT, nil, TOPLEFT, x, y)
        else
            menuToAnchor:SetAnchor(BOTTOMLEFT, nil, TOPLEFT, x, y)
        end
    else
        if bottom then
            menuToAnchor:SetAnchor(TOPRIGHT, nil, TOPLEFT, x, y)
        else
            menuToAnchor:SetAnchor(BOTTOMRIGHT, nil, TOPLEFT, x, y)
        end
    end
end

local function UpdateMenuDimensions(menuEntry)
    if ZO_Menu.currentIndex > 0 then
        local textWidth, textHeight = menuEntry.item.nameLabel:GetTextDimensions()
        local checkboxWidth, checkboxHeight = 0, 0
        if menuEntry.checkbox then
            checkboxWidth, checkboxHeight = menuEntry.checkbox:GetDesiredWidth(), menuEntry.checkbox:GetDesiredHeight()
        end

        local entryWidth = textWidth + checkboxWidth + ZO_Menu.menuPad * 2
        local entryHeight = zo_max(textHeight, checkboxHeight)

        if entryWidth > ZO_Menu.width then
            ZO_Menu.width = entryWidth
        end

        ZO_Menu.height = ZO_Menu.height + entryHeight + menuEntry.itemYPad

        -- More adjustments will come later...this just needs to set the height
        -- HACK: Because anchor processing doesn't happen right away, and because GetDimensions
        -- does NOT return desired dimensions...this will actually need to remember the height
        -- that the label was set to.  And to remember it, we need to find the menu item in the 
        -- appropriate menu...
        menuEntry.item.storedHeight = entryHeight
    end
end

function ClearMenu()
    local owner = ZO_Menu.owner

    ZO_Menu:SetHidden(true)
    SetAddMenuItemCallback(nil) -- just in case this wasn't cleared
    SetMenuHiddenCallback(nil)
    ZO_MenuHighlight:SetHidden(true)
    ZO_Menu_SetSelectedIndex(nil)

    if ZO_Menu.itemPool then
        ZO_Menu.itemPool:ReleaseAllObjects()
    end

    if ZO_Menu.checkBoxPool then
        ZO_Menu.checkBoxPool:ReleaseAllObjects()
    end

    ZO_Menu.highlightPool:ReleaseAllObjects()

    ZO_Menu.nextAnchor = ZO_Menu

    ZO_Menu:SetDimensions(0, 0)
    ZO_Menu.currentIndex = 1
    ZO_Menu.width = 0
    ZO_Menu.height = 0
    ZO_Menu.items = {}
    ZO_Menu.spacing = 0
    ZO_Menu.menuPad = 8
    ZO_Menu.owner = nil

    if type(owner) == "userdata" then
        owner:SetHandler("OnEffectivelyHidden", nil, "ZO_Menu")
    end
end

function IsMenuVisible()
    return mouseUpRefCounts[ZO_Menu] ~= nil
end

function SetMenuMinimumWidth(minWidth)
    ZO_Menu.width = minWidth
end

function SetMenuSpacing(spacing)
    ZO_Menu.spacing = spacing
end

function SetMenuPad(menuPad)
    ZO_Menu.menuPad = menuPad
end

local function SetMenuOwner(owner)
    if type(owner) == "userdata" then
        owner:SetHandler("OnEffectivelyHidden", ClearMenu, "ZO_Menu")
    end

    ZO_Menu.owner = owner
end

function GetMenuOwner()
    return ZO_Menu.owner
end

function MenuOwnerClosed(potentialOwner)
    if IsMenuVisible() and (GetMenuOwner() == potentialOwner) then
        ClearMenu()
    end
end

-- NOTE: If owner is a valid control, the menu system will use this to install an OnEffectivelyHidden handler
-- so that if the control is hidden for any reason, the menu will properly close.  The handler is currently
-- overwritten/removed, so make sure that you account for that if your controls need to have their own
-- effectively hidden handlers.
function ShowMenu(owner, initialRefCount, menuType)
    if next(ZO_Menu.items) == nil then
        return false
    end

    menuType = menuType or MENU_TYPE_DEFAULT

    ZO_Menu:SetDimensions((ZO_Menu.menuPad * 2) + ZO_Menu.width, (ZO_Menu.menuPad * 2) + ZO_Menu.height + ZO_Menu.spacing * (#ZO_Menu.items - 1))

    -- Force the control that contains this label to the same size as the label
    -- so that mouse over and item click behavior works on each line without 
    -- needing to mouse over the actual text of the label.    
    -- Keep the height the same as it was...
    for k, v in pairs(ZO_Menu.items) do
        -- HACK: See note below about storing the height...
        v.item:SetDimensions(ZO_Menu.width - ZO_Menu.menuPad, v.item.storedHeight)
    end
    
    if ZO_Menu.menuType ~= menuType then
        local info = menuInfo[menuType]
        ZO_MenuBG:SetCenterTexture(info.backdropCenter)
        ZO_MenuBG:SetEdgeTexture(info.backdropEdge, info.backdropEdgeWidth, info.backdropEdgeHeight)
        ZO_MenuBG:SetInsets(unpack(info.backdropInsets))
        ZO_MenuBGMungeOverlay:SetHidden(info.hideMunge)
    end

    ZO_Menu.menuType = menuType

    ZO_Menu:SetHidden(false)
    ZO_Menus:BringWindowToTop()
    ZO_Menu.underlayControl:SetHidden(not ZO_Menu.useUnderlay)

    AnchorMenuToMouse(ZO_Menu)

    -- Combobox will set this to two so that the first mouse up doesn't close the menu
    -- Otherwise, the first mouse up will close the menu.
    mouseUpRefCounts[ZO_Menu] = initialRefCount or 2

    -- The menu has been shown, get rid of the add item callback; it will be set again for other menus.
    SetAddMenuItemCallback(nil)

    SetMenuOwner(owner)

    return true
end

function AnchorMenu(control, offsetY)
    -- By default, the menu is shown where the mouse cursor is...
    -- but if valid anchor data is passed in, that's used instead.
    
    ZO_Menu:ClearAnchors()
    ZO_Menu:SetAnchor(TOPLEFT, control, BOTTOMLEFT, 0, offsetY)

    -- The menu must properly contain its contents, but if "control" is
    -- larger than the contents' widths, use "control" to size the menu.
    -- NOTE: Not using padding on purpose since I don't want to redo all the options dropdowns.  If something else breaks this, fix those by increasing their min width
    if control:GetWidth() >= ZO_Menu.width then
        ZO_Menu:SetAnchor(TOPRIGHT, control, BOTTOMRIGHT, 0, offsetY)
    end
end

function SetAddMenuItemCallback(itemAddedCallback)
    if itemAddedCallback and type(itemAddedCallback) == "function" then
        currentOnMenuItemAddedCallback = itemAddedCallback
    else
        currentOnMenuItemAddedCallback = nil
    end
end

function SetMenuHiddenCallback(menuHiddenCallback)
    if menuHiddenCallback and type(menuHiddenCallback) == "function" then
        currentOnMenuHiddenCallback = menuHiddenCallback
    else
        currentOnMenuHiddenCallback = nil
    end
end

function GetMenuPadding()
    return ZO_Menu.menuPad
end

--onSelect is a required field
function AddMenuItem(labelText, onSelect, itemType, labelFont, normalColor, highlightColor, itemYPad, horizontalAlignment, isHighlighted, onEnter, onExit, enabled)
    if not internalassert(onSelect, "AddMenuItem requires a valid onSelect function") then
        -- ESO-804925
        return
    end

    local isEnabled = enabled
    if isEnabled == nil then
        -- Evaluate nil to be equivalent to true for backwards compatibility.
        isEnabled = true
    end

    local menuItemControl = ZO_Menu.itemPool:AcquireObject()
    menuItemControl.OnSelect = onSelect
    menuItemControl.menuIndex = ZO_Menu.currentIndex
    menuItemControl.enabled = isEnabled
    menuItemControl.onEnter = onEnter
    menuItemControl.onExit = onExit

    local checkboxItemControl
    if itemType == MENU_ADD_OPTION_CHECKBOX then
        checkboxItemControl = ZO_Menu.checkBoxPool:AcquireObject()
        ZO_CheckButton_SetToggleFunction(checkboxItemControl, onSelect)
        checkboxItemControl.menuIndex = ZO_Menu.currentIndex
    end

    local addedItemIndex = ZO_Menu.currentIndex
    ZO_Menu.currentIndex = ZO_Menu.currentIndex + 1

    itemYPad = itemYPad or 0

    table.insert(ZO_Menu.items, { item = menuItemControl, checkbox = checkboxItemControl, itemYPad = itemYPad })

    local nameLabel = menuItemControl.nameLabel

    menuItemControl:ClearAnchors()
    menuItemControl:SetHidden(false)
    -- We need to reset these anchors first so the label can get it's proper size for the parent to reference
    nameLabel:ClearAnchors()
    nameLabel:SetAnchor(TOPLEFT)

    if checkboxItemControl then
        checkboxItemControl:ClearAnchors()
        checkboxItemControl:SetHidden(false)
    end

    if ZO_Menu.nextAnchor == ZO_Menu then
        if checkboxItemControl then
            checkboxItemControl:SetAnchor(TOPLEFT, ZO_Menu.nextAnchor, TOPLEFT, ZO_Menu.menuPad, ZO_Menu.menuPad + itemYPad)
            menuItemControl:SetAnchor(TOPLEFT, checkboxItemControl, TOPRIGHT, 0, 0)
        else
            menuItemControl:SetAnchor(TOPLEFT, ZO_Menu.nextAnchor, TOPLEFT, ZO_Menu.menuPad, ZO_Menu.menuPad + itemYPad)
        end
    else
        if checkboxItemControl then
            checkboxItemControl:SetAnchor(TOPLEFT, ZO_Menu.nextAnchor, BOTTOMLEFT, 0, ZO_Menu.spacing + itemYPad)
            menuItemControl:SetAnchor(TOPLEFT, checkboxItemControl, TOPRIGHT, 0, 0)
        else
            menuItemControl:SetAnchor(TOPLEFT, ZO_Menu.nextAnchor, BOTTOMLEFT, 0, ZO_Menu.spacing + itemYPad)
        end
    end

    ZO_Menu.nextAnchor = checkboxItemControl or menuItemControl

    if labelFont == nil then
        if not IsInGamepadPreferredMode() then
            labelFont = "ZoFontGame"
        else
            labelFont = "ZoFontGamepad22"
        end
    end

    nameLabel.normalColor = normalColor or DEFAULT_TEXT_COLOR
    nameLabel.highlightColor = highlightColor or DEFAULT_TEXT_HIGHLIGHT

    -- NOTE: Must set text AFTER the current index has been incremented.
    nameLabel:SetFont(labelFont)
    nameLabel:SetText(labelText)
    UpdateMenuDimensions(ZO_Menu.items[#ZO_Menu.items])

    -- Now that we know how the label wil size, finish the anchors if we need the label to be right aligned
    -- Right align won't work if the label has no width
    horizontalAlignment = horizontalAlignment or TEXT_ALIGN_LEFT
    nameLabel:SetHorizontalAlignment(horizontalAlignment)
    if horizontalAlignment == TEXT_ALIGN_RIGHT then
        nameLabel:SetAnchor(TOPRIGHT)
    end

    nameLabel:SetColor(nameLabel.normalColor:UnpackRGBA())

    if isHighlighted then
        menuItemControl.isHighlighted = isHighlighted
        ZO_Menu_AcquireAndApplyHighlight(menuItemControl)
    end

    if currentOnMenuItemAddedCallback then
        currentOnMenuItemAddedCallback()
    end

    return addedItemIndex
end

function UpdateMenuItemState(item, state)
    local menuEntry = ZO_Menu.items[item]
    if menuEntry and menuEntry.checkbox then
        ZO_CheckButton_SetCheckState(menuEntry.checkbox, state)
    end
end

function ZO_Menu_SelectItem(control)
    ZO_MenuHighlight:ClearAnchors()

    ZO_MenuHighlight:SetAnchor(TOPLEFT, control, TOPLEFT, -2, -2)
    ZO_MenuHighlight:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, 2, 2)

    ZO_MenuHighlight:SetHidden(false)

    if not control.isHighlighted then
        control.nameLabel:SetColor(control.nameLabel.highlightColor:UnpackRGBA())
    end
end

function ZO_Menu_UnselectItem(control)
    ZO_MenuHighlight:SetHidden(true)

    if not control.isHighlighted then
        control.nameLabel:SetColor(control.nameLabel.normalColor:UnpackRGBA())
    end
end

function ZO_Menu_SetSelectedIndex(index)
    if not ZO_Menu.items then
        return
    end

    if index then
        index = zo_max(zo_min(index, #ZO_Menu.items), 1)
    end

    if selectedIndex ~= index then
        if selectedIndex then
            local entry = ZO_Menu.items[selectedIndex]
            if entry then
                local control = entry.item
                if control then
                    ZO_Menu_UnselectItem(control)
                end
            end
        end

        selectedIndex = index

        if selectedIndex then
            local entry = ZO_Menu.items[selectedIndex]
            if entry then
                local control = entry.item

                if control then
                    ZO_Menu_SelectItem(control)
                end
            end
        end
    end
end

function ZO_Menu_GetNumMenuItems()
    return #ZO_Menu.items
end

function ZO_Menu_GetSelectedIndex()
    return selectedIndex
end

function ZO_Menu_GetSelectedText()
    local control = ZO_Menu.items[selectedIndex].item
    if control then
        return control.nameLabel:GetText()
    end
end

function ZO_Menu_EnterItem(control)
    ZO_Menu_SetSelectedIndex(control.menuIndex)

    if type(control.onEnter) == "function" then
        control.onEnter(control)
    end
end

function ZO_Menu_ExitItem(control)
    if selectedIndex == control.menuIndex then
        ZO_Menu_SetSelectedIndex(nil)

        if type(control.onExit) == "function" then
            control.onExit(control)
        end
    end
end

function ZO_Menu_ClickItem(control, button)
    if button == MOUSE_BUTTON_INDEX_LEFT then
        ZO_Menu_SetLastCommandWasFromMenu(true)
        local menuEntry = ZO_Menu.items[control.menuIndex]
        if menuEntry and menuEntry.checkbox then
            -- Treat this like the checkbox was clicked.
            ZO_CheckButton_OnClicked(menuEntry.checkbox, button)
        else
            -- Treat it like the label was clicked
            if control.OnSelect then
                control.OnSelect()
            else
                -- ESO-804925
                internalassert(false, string.format("Attempting to click menu entry [%s] but it has no OnSelect behavior defined.", control.nameLabel:GetText()))
            end
            
            if control.enabled ~= false then
                if ZO_Menu.menuType == MENU_TYPE_MULTISELECT_COMBO_BOX then
                    control.isHighlighted = not control.isHighlighted

                    if control.isHighlighted then
                        ZO_Menu_AcquireAndApplyHighlight(control)
                    else
                        ZO_Menu_ReleaseHighlight(control)
                    end
                else
                    ClearMenu()
                end
            end
        end
    end
end

function ZO_Menu_OnHide(control)
    mouseUpRefCounts[ZO_Menu] = nil
    ZO_Menu.useUnderlay = false
    if currentOnMenuHiddenCallback then
        currentOnMenuHiddenCallback()
    end
end

function ZO_Menu_AcquireAndApplyHighlight(control)
    local highlight, key = ZO_Menu.highlightPool:AcquireObject()
    highlight.key = key
    control.highlight = highlight

    highlight:SetAnchor(TOPLEFT, control, TOPLEFT, -2, -2)
    highlight:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, 2, 2)

    highlight:SetHidden(false)

    control.nameLabel:SetColor(control.nameLabel.highlightColor:UnpackRGBA())
end

function ZO_Menu_ReleaseHighlight(control)
    ZO_Menu.highlightPool:ReleaseObject(control.highlight.key)
    control.highlight = nil
end

local function OnGlobalMouseUp()
    local refCount = mouseUpRefCounts[ZO_Menu]
    if refCount ~= nil then
        local moc = WINDOW_MANAGER:GetMouseOverControl()
        if moc:GetOwningWindow() ~= ZO_Menus or moc == ZO_Menu.underlayControl then
            refCount = refCount - 1
            mouseUpRefCounts[ZO_Menu] = refCount
            if refCount <= 0 then
                ClearMenu()
            end
        end
    end
end

local function ResetFunction(control)
    control:SetHidden(true)
    control:ClearAnchors()
    control.OnSelect = nil
    control.menuIndex = nil
    control.isHighlighted = nil
end

local function ResetCheckbox(checkbox)
    ResetFunction(checkbox)
    ZO_CheckButton_SetToggleFunction(checkbox, nil)
    ZO_CheckButton_SetUnchecked(checkbox)
end

local function EntryFactory(pool)
    local control = ZO_ObjectPool_CreateControl("ZO_MenuItem", pool, ZO_Menu)
    control.nameLabel = control:GetNamedChild("Name")
    return control
end

local function CheckBoxFactory(pool)
    return ZO_ObjectPool_CreateControl("ZO_MenuItemCheckButton", pool, ZO_Menu)
end

local function HighlightFactory(pool)
    return ZO_ObjectPool_CreateControl("ZO_MenuItemHighlight", pool, ZO_Menu)
end

local function HighlightResetFunction(control)
    control:SetHidden(true)
    control:ClearAnchors()
    control.key = nil
end

function ZO_Menu_Initialize()
    --Pre-allocate these so the control closures (for OnMouseUp) are created as secure code and add-ons can use them.
    ZO_Menu.itemPool = ZO_ObjectPool:New(EntryFactory, ResetFunction)
    for i = 1, 30 do
        ZO_Menu.itemPool:AcquireObject()
    end
    ZO_Menu.itemPool:ReleaseAllObjects()

    ZO_Menu.checkBoxPool = ZO_ObjectPool:New(CheckBoxFactory, ResetCheckbox)
    for i = 1, 30 do
        ZO_Menu.checkBoxPool:AcquireObject()
    end
    ZO_Menu.checkBoxPool:ReleaseAllObjects()

    ZO_Menu.highlightPool = ZO_ObjectPool:New(HighlightFactory, HighlightResetFunction)

    ZO_Menu.underlayControl = ZO_Menu:GetNamedChild("Underlay")

    ClearMenu()
 
    EVENT_MANAGER:RegisterForEvent("ZO_Menu_OnGlobalMouseUp", EVENT_GLOBAL_MOUSE_UP, OnGlobalMouseUp)
end

function ZO_Menu_WasLastCommandFromMenu()
    return lastCommandWasFromMenu
end

function ZO_Menu_SetLastCommandWasFromMenu(menuCommand)
    lastCommandWasFromMenu = menuCommand
end

function ZO_Menu_SetUseUnderlay(useUnderlay)
    ZO_Menu.useUnderlay = useUnderlay
end