local MAX_NUM_DIALOGS = 1
local NUM_DIALOG_BUTTONS = 2
local RELEASED_FROM_BUTTON_PRESS = true
local BUTTON_SPACING = 20

-- This used to be a table of displayed dialogs, but more than one hasn't been supported since 2013.
-- It has now been simplified to a single displayed dialog table (name and dialog object) but all of the existing
-- functions have been maintained for backward compatibility. Some functions might seem odd in this context
-- (i.e. ZO_Dialogs_FindDialog) but it's only to maintain that compatibility
local g_displayedDialog = nil
local g_dialogQueue = {}
local g_isDialogQueuePaused = false
local g_dialogPauseExemptionsListOrFunction = nil
local g_currencyPool = nil
local g_curInstanceId = 0

local QUEUED_DIALOG_INDEX_NAME = 1
local QUEUED_DIALOG_INDEX_DATA = 2
local QUEUED_DIALOG_INDEX_PARAMS = 3

ZO_DIALOG_SYNC_OBJECT = GetOrCreateSynchronizingObject("dialog")

local function ContainsName(testName, ...)
    for i = 1, select("#", ...) do
        local name = select(i, ...)
        if name == testName then
            return true
        end
    end
    return false
end

local function QueueDialog(name, data, params, isGamepad, dialogInfo, queueInFront)
    if name and dialogInfo and dialogInfo.onlyQueueOnce then
        -- If dialog is the same as the currently shown dialog don't queue it.
        if name == g_displayedDialog.name then
            return
        end

        -- If the dialog is already queued and can only be queued once, don't requeue it
        for _, dialog in ipairs(g_dialogQueue) do
            if dialog[QUEUED_DIALOG_INDEX_NAME] == name then
                return
            end
        end
    end

    name = name or ""
    data = data or {}
    params = params or {}
    if queueInFront then
        table.insert(g_dialogQueue, 1, {name, data, params, isGamepad})
    else
        table.insert(g_dialogQueue, {name, data, params, isGamepad})
    end
end

local function RemoveQueuedDialogs(name, filterFunction)
    local i = 1
    while i <= #g_dialogQueue do
        local dialog = g_dialogQueue[i]

        if name == dialog[QUEUED_DIALOG_INDEX_NAME] and (not filterFunction or filterFunction(dialog[QUEUED_DIALOG_INDEX_DATA])) then
            table.remove(g_dialogQueue, i)
        else
            i = i + 1
        end
    end
end

local function RemoveQueuedDialogsExcept(...)
    local i = 1
    while i <= #g_dialogQueue do
        local dialog = g_dialogQueue[i]

        if not ContainsName(dialog[QUEUED_DIALOG_INDEX_NAME], ...) then
            table.remove(g_dialogQueue, i)
        else
            i = i + 1
        end
    end
end

local function GetDisplayedDialog()
    return g_displayedDialog and g_displayedDialog.dialog or nil
end

function ZO_Dialogs_IsShowingDialog()
    return g_displayedDialog ~= nil
end

function ZO_Dialogs_IsShowing(nameOrDialog)
    if g_displayedDialog then
        if type(nameOrDialog) == "string" then
            return g_displayedDialog.name == nameOrDialog
        else
            return g_displayedDialog.dialog == nameOrDialog
        end
    end

    return false
end

function ZO_Dialogs_FindDialog(nameOrDialog, filterFunction)
    if ZO_Dialogs_IsShowing(nameOrDialog) and (not filterFunction or filterFunction(g_displayedDialog.dialog.data)) then
        return g_displayedDialog.dialog
    end
    return nil
end

function ZO_Dialogs_IsDialogHiding(nameOrDialog)
    local dialog = ZO_Dialogs_FindDialog(nameOrDialog)

    return dialog and dialog.hiding
end

function ZO_Dialogs_IsShowingDialogThatShouldShowTooltip()
    local displayedDialog = GetDisplayedDialog()
    if not displayedDialog then
        return false
    end

    return displayedDialog.shouldShowTooltip
end

local function HandleCallback(clickedButton)
    local dialog = GetDisplayedDialog()
    if dialog then
        local instanceId = dialog.instanceId
        if clickedButton.m_callback then
            clickedButton.m_callback(dialog)
        end

        --Make sure the dialog wasn't released and then reshown
        if clickedButton.m_noReleaseOnClick == nil and dialog.instanceId == instanceId then
            ZO_Dialogs_ReleaseDialog(dialog, RELEASED_FROM_BUTTON_PRESS)
        end
    end
end

function ZO_Dialogs_SetupCustomButton(button, text, keybind, clickSound, callback)
    button:SetKeybind(keybind)
    button:SetText(text)
    button:SetClickSound(clickSound)
    button.m_callback = callback
end

local function GetDialog(isGamepad)
    local dialogBaseName = "ZO_Dialog"
    if isGamepad then
        dialogBaseName = "ZO_DialogGamepad"
    end

    for i = 1, MAX_NUM_DIALOGS do
        local dialog = GetControl(dialogBaseName..i)
        if(dialog:IsControlHidden()) then
            ZO_Dialogs_InitializeDialog(dialog, isGamepad)
            return dialog
        end
    end
    return nil
end

local function GetFormattedDialogText(text, params)
    if text then
        if params and #params > 0 then
            text = zo_strformat(text, unpack(params))
        elseif type(text) == "number" then
            text = GetString(text)
        end
    else
        text = ""
    end

    return text
end

function ZO_GetFormattedDialogText(dialog, textTable, params)
    if not textTable then
        return
    end

    local timer = textTable.timer
    if params and type(timer) == "number" and type(params[timer]) == "number" then
        local timerParam = params[timer]
        if textTable.verboseTimer then
            timerParam = ZO_FormatTimeMilliseconds(timerParam, TIME_FORMAT_STYLE_DESCRIPTIVE)
        else
            timerParam = ZO_FormatTimeMilliseconds(timerParam, TIME_FORMAT_STYLE_DESCRIPTIVE_SHORT_SHOW_ZERO_SECS)
        end
        params[timer] = timerParam
    end
    
    local textOrCallback = textTable.text
    local finalText
    if type(textOrCallback) == "function" then
        finalText = textOrCallback(dialog)
    else
        finalText = textOrCallback
    end

    local formattedText = GetFormattedDialogText(finalText, params)

    return formattedText
end

local function SetDialogTextFormatted(dialog, textControl, textTable, params)
    local formattedText = ZO_GetFormattedDialogText(dialog, textTable, params)

    if not textControl or not formattedText then
        return
    end

    textControl:SetText(formattedText)
    textControl:SetHidden(false)

    if textTable.align then
        textControl:SetHorizontalAlignment(textTable.align)
    end

    return select(2, textControl:GetTextDimensions())
end

function ZO_Dialogs_SetDialogLoadingIcon(loadingIcon, textControl, showLoadingIconData)
    local shouldShowLoadingIcon = false
    local iconAnchor
    local showType = type(showLoadingIconData)

    if showType == "boolean" then
        shouldShowLoadingIcon = showLoadingIconData
    elseif showType == "table" then
        shouldShowLoadingIcon = true
        iconAnchor = showLoadingIconData
    end

    if shouldShowLoadingIcon then
        loadingIcon:Show()

        if not iconAnchor then
            local horizontalAlignment = textControl:GetHorizontalAlignment()
            loadingIcon:ClearAnchors()
            if horizontalAlignment == TEXT_ALIGN_LEFT then
                loadingIcon:SetAnchor(RIGHT, textControl, LEFT, -5, 0)
            elseif horizontalAlignment == TEXT_ALIGN_RIGHT then
                loadingIcon:SetAnchor(LEFT, textControl, RIGHT, 5, 0)
            else
                local textWidth = textControl:GetTextDimensions()
                loadingIcon:SetAnchor(RIGHT, textControl, CENTER, -textWidth * 0.5 - 5, 0)
            end
        else
            iconAnchor:Set(loadingIcon)
        end
    else
        loadingIcon:Hide()
    end
end

local function ReanchorDialog(dialog, isGamepad)
    if(not dialog) then
        return
    end

    local dialogNumber = dialog.id

    dialog:ClearAnchors()

    if isGamepad then
        if(dialogNumber == 1) then
            local dialogBackground = dialog:GetNamedChild("Bg")
            local anchor1 = ZO_GamepadGrid_GetNavAnchor(GAMEPAD_GRID_NAV1, 1)
            anchor1:AddToControl(dialogBackground)
            ZO_GamepadGrid_GetNavAnchor(GAMEPAD_GRID_NAV1, 2):AddToControl(dialogBackground)

            dialog:SetAnchor(LEFT, GuiRoot, LEFT, anchor1:GetOffsetX())
        end
    else
        if dialogNumber == 1 then
            dialog:SetAnchor(CENTER, GuiRoot, CENTER, 0, -55)
        elseif dialogNumber == 2 then
            dialog:SetAnchor(TOP, ZO_Dialog1, BOTTOM, 0, 24)
        elseif dialogNumber == 3 then
            dialog:SetAnchor(BOTTOM, ZO_Dialog1, TOP, 0, -24)
        elseif dialogNumber == 4 then
            dialog:SetAnchor(TOP, ZO_Dialog2, BOTTOM, 0, 24)
        end
    end
end

local function GetButtonControl(dialog, index)
    local dialogInfo = dialog.info
    if index <= dialog.numButtons then
        if dialog.buttonControls then
            return dialog.buttonControls[index]
        end

        if dialogInfo.customControl and dialogInfo.buttons then
            return dialogInfo.buttons[index].control
        end
    end
end

function ZO_Dialogs_ShowPlatformDialog(...)
    local dialogFn = IsInGamepadPreferredMode() and ZO_Dialogs_ShowGamepadDialog or ZO_Dialogs_ShowDialog
    dialogFn(...)
end

local function RefreshMainText(dialog, dialogInfo, textParams)
    if not textParams then
        textParams = {}
    end

    local mainText
    local textControl

    local isGamepadDialog = dialog.isGamepad and dialogInfo.gamepadInfo and dialogInfo.gamepadInfo.dialogType -- There is a legacy gamepad dialog still in use (for now).
    if isGamepadDialog then
        local title = ZO_GetFormattedDialogText(dialog, dialogInfo.title, textParams.titleParams)
        mainText = ZO_GetFormattedDialogText(dialog, dialogInfo.mainText, textParams.mainTextParams)
        local warningText = ZO_GetFormattedDialogText(dialog, dialogInfo.warning, textParams.warningParams)
        local subText = ZO_GetFormattedDialogText(dialog, dialogInfo.subText, textParams.subTextParams)
        ZO_GenericGamepadDialog_RefreshText(dialog, title, mainText, warningText, subText)
    else
        textControl = dialog:GetNamedChild("Text")
        mainText = dialogInfo.mainText
        if textControl then
            if mainText then
                if type(mainText) == "function" then
                    dialog.mainText = mainText(dialog)
                else
                    dialog.mainText = mainText
                end

                if dialog.mainText.lineSpacing then
                    textControl:SetLineSpacing(dialog.mainText.lineSpacing)
                else
                    textControl:SetLineSpacing(0)
                end

                SetDialogTextFormatted(dialog, textControl, dialog.mainText, textParams.mainTextParams)
            else
                textControl:SetText(nil)
                textControl:SetHidden(true)
            end 
        end
    end

    return mainText, textControl
end

function ZO_Dialogs_RefreshDialogText(name, dialog, textParams)
    local dialogInfo = ESO_Dialogs[name]
    if type(dialogInfo) ~= "table" then
        return
    end

    if ZO_Dialogs_IsShowingDialog() then
        RefreshMainText(dialog, dialogInfo, textParams)
    end
end

-- To show a gamepad style sidebar dialog, call this function.
-- See comments about ZO_Dialogs_ShowDialog for more information
function ZO_Dialogs_ShowGamepadDialog(name, data, textParams)
    local IS_GAMEPAD = true
    local currentScene = SCENE_MANAGER:GetCurrentScene()
    local dialog = ESO_Dialogs[name]
    if currentScene and currentScene:IsShowing() then
        ZO_Dialogs_ShowDialog(name, data, textParams, IS_GAMEPAD)
    elseif dialog.gamepadInfo and dialog.gamepadInfo.allowShowOnNextScene and SCENE_MANAGER:GetNextScene() then
        -- if we are waiting for the scene to change and ask to show the same dialog multiple times, only use the latest call's data
        if dialog.gamepadInfo.nextSceneCallback then
            SCENE_MANAGER:UnregisterCallback("SceneStateChanged", dialog.gamepadInfo.nextSceneCallback)
        end

        dialog.gamepadInfo.nextSceneCallback = function(scene, oldState, newState)
            if newState == SCENE_SHOWN then
                SCENE_MANAGER:UnregisterCallback("SceneStateChanged", dialog.gamepadInfo.nextSceneCallback)
                ZO_Dialogs_ShowGamepadDialog(name, data, textParams)
                dialog.gamepadInfo.nextSceneCallback = nil
            end
        end
        SCENE_MANAGER:RegisterCallback("SceneStateChanged", dialog.gamepadInfo.nextSceneCallback)
    else
        if dialog.noChoiceCallback then
            dialog.data = data
            dialog.noChoiceCallback(dialog)
        end
    end
end

-- To show a dialog, call this function. 
-- The first parameter should be the name of the dialog (as definied in either InGameDialogs or PreGameDialogs). 
-- The second parameter should be an array table, containing any data that will be needed in the callback functions in the dialog
--      you want to display. 
-- The third parameter is a table, which contains parameters used when filling out the strings in your dialog.
--
-- If the main text in the dialog has 2 parameters (e.g "Hello <<1>> <<2>>"), then the 3rd parameter should contain a subtable called
-- mainTextParams which itself contains 2 members, the first will go into the <<1>> and the second will go into the <<2>>. The 3rd parameter
-- in ZO_Dialogs_ShowDialog can also contain a titleParams subtable which is used to fill in the parameters in the title, if needed.
--
-- If the sub text in the dialog has 2 parameters (e.g "Hello <<1>> <<2>>"), then the 3rd parameter should contain a subtable called
-- subTextParams which itself contains 2 members, the first will go into the <<1>> and the second will go into the <<2>>.
--
-- So as an example, let's say you had defined a dialog in InGameDialogs called "TEST_DIALOG" with
--      title = { text = "Dialog <<1>>" } and mainText = { text = "Main <<1>> Text <<2>>" } and subText = { text = "Sub <<1>> Text <<2>>" }
-- And you called
--      ZO_Dialogs_ShowDialog("TEST_DIALOG", {5}, {titleParams={"Test1"}, mainTextParams={"Test2", "Test3"},  subTextParams={"Test4", "Test5"}})
-- The resulting dialog would have a title that read "Dialog Test1" and a main text field that read "Main Test2 Text Test3" and a sub text field that read "Sub Test4 Text Test5".
-- The 5 passed in the second parameter could be used by the callback functions to perform various tasks based on this value.

-- Dialogs themselves (see InGameDialogs.lua, etc.) must contain at least a "mainText" table, with at least the "text" member.
-- mainText.text is filled in using the mainTextParams subtable of the table passed in the 3rd parameter to ZO_Dialogs_ShowDialog.

-- The mainText table can also optionally contain:
-- An "align" member to set the alignment of the text (TEXT_ALIGN_LEFT, TEXT_ALIGN_RIGHT, or TEXT_ALIGN_CENTER....left is default).
-- A "timer" field, which indicates that a certain parameter should be treated as a sceonds in a timer, and converted to time format
--      (so if mainText contains "timer = 1", the 1st parameter in mainText.text is converted to time format before being placed
--      in the string).
--
-- subText should just not be shown if not set and will not be shown if no mainText exists
--
-- Dialogs can also optionally contain:
-- 
-- A "title" table, which works the same way as "mainText" ("text", "align" and "title" fields are allowed), no title is shown if "title" is not set.
-- A "noChoiceCallback" field, which is executed when the dialog is closed without making a choice first.
-- A "hideSound" field, which is a sound id to be played when the dialog is closed without selecting an option
-- An "updateFn" field, which should be a function. If present, this function is called on each update when this dialog is showing.
-- An "editBox" field, which adds an edit box to the dialog. It can specify:
--      textType = The type of input the edit box accepts.
--      To get the value in the editbox, use ZO_Dialogs_GetEditBoxText.
-- A "warning" table, which works the same way as "mainText", which shows some red text at the bottom of the dialog to call attention to the specific action that is occuring
-- Finally, the is a "buttons" table, in which each member corresponds to a button. Dialogs support a maximum of 2 buttons.
-- If the buttons table is present, each of it's members in turn MUST contain a "text" field. Also, each button can optionally contain:
--      A "callback" function field (whose first parameter should always be "dialog"....use "dialog.data[i]" to reference the ith data member passed in).
--      A "clickSound" field that defines what sound to play when the button is clicked.
-- An option to show an animated loading icon near the main text, called "showLoadingIcon"
--
-- See the "DESTROY_AUGMENT_PROMPT" and "DEATH_PROMPT" dialogs in InGameDialogs.lua for examples of dialogs that use these various fields.
function ZO_Dialogs_ShowDialog(name, data, textParams, isGamepad)
    -- Get the dialog info from the ESO_Dialogs table
    local dialogInfo = ESO_Dialogs[name]
    if type(dialogInfo) ~= "table" then
        return nil
    end

    local forceQueueDialog = false
    local queueInFront = false
    if g_isDialogQueuePaused then
        forceQueueDialog = true
        if ZO_Dialogs_IsDialogExemptFromDialogQueuePause(name, data) then
            forceQueueDialog = false
            queueInFront = true
        end
    end

    if ZO_Dialogs_IsShowingDialog() or forceQueueDialog then
        if dialogInfo.canQueue then
            QueueDialog(name, data, textParams, isGamepad, dialogInfo, queueInFront)
        end
        return nil
    end

    --Acquire Dialog Control
    ------------------------------
    local dialog

    local isGamepadDialog = isGamepad and dialogInfo.gamepadInfo and dialogInfo.gamepadInfo.dialogType  -- There is a legacy gamepad dialog still in use (for now).
    local isGenericGamepadDialog = isGamepadDialog and dialogInfo.gamepadInfo.dialogType ~= GAMEPAD_DIALOGS.CUSTOM
    if isGenericGamepadDialog then
        dialog = ZO_GenericGamepadDialog_GetControl(dialogInfo.gamepadInfo.dialogType)

        if not dialog then
            return nil
        end
    elseif dialogInfo.customControl then
        if type(dialogInfo.customControl) == "function" then
            dialog = dialogInfo.customControl()
        else
            dialog = dialogInfo.customControl
        end

        if not dialog then
            return nil
        end
    else
        dialog = GetDialog(isGamepad)

        if not dialog then
            -- Dialog can't be created right now, so place it in a queue to be created later
            QueueDialog(name, data, textParams, isGamepad)
            return nil
        end
    end

    dialog.isGamepad = isGamepad

    --Shared Init
    ------------------

    --Clear focus in case the dialog came up when we had an edit control focused.
    WINDOW_MANAGER:SetFocusByName()

    dialog.info = dialogInfo
    dialog.data = data
    dialog.textParams = textParams

    --Title

    local titleControl = dialog:GetNamedChild("Title")
    local title = dialogInfo.title

    if not textParams then
        textParams = {}
    end

    if title then
        SetDialogTextFormatted(dialog, titleControl, title, textParams.titleParams)
    elseif titleControl and isGamepad then
        SetDialogTextFormatted(dialog, titleControl, "")
    end

    -- Warning Text
    local warningLabel = dialog:GetNamedChild("WarningText")
    local warning = dialogInfo.warning

    if warning then
        SetDialogTextFormatted(dialog, warningLabel, warning, textParams.warningParams)
    elseif warningLabel and isGamepad then
        SetDialogTextFormatted(dialog, warningLabel, "")
    end

    --Buttons

    local buttonInfos = dialogInfo.buttons
    local numButtonInfos = buttonInfos and #buttonInfos or 0
    dialog.numButtons = numButtonInfos
    if numButtonInfos > 0 and not isGamepadDialog then
        for i = 1, numButtonInfos do
            local buttonInfo = buttonInfos[i]
            local button = GetButtonControl(dialog, i)

            local buttonVisible = true
            if buttonInfo.visible ~= nil then
                if type(buttonInfo.visible) == "function" then
                    buttonVisible = buttonInfo.visible(dialog)
                else
                    buttonVisible = buttonInfo.visible
                end
            end

            if not buttonVisible then
                button:SetHidden(true)
                button:SetKeybindEnabled(false)
            else
                local buttonText
                if textParams and textParams.buttonTextOverrides and textParams.buttonTextOverrides[i] then
                    buttonText = textParams.buttonTextOverrides[i]
                elseif type(buttonInfo.text) == "number" then
                    buttonText = GetString(buttonInfo.text)
                elseif type(buttonInfo.text) == "function" then
                    buttonText = buttonInfo.text(dialog)
                else
                    buttonText = buttonInfo.text
                end
                button:SetText(buttonText)
                button:SetHidden(false)
                button.m_callback = buttonInfo.callback
                button.m_noReleaseOnClick = buttonInfo.noReleaseOnClick

                local keybind
                local hasKeybind = true
                if buttonInfo.keybind then
                    if type(buttonInfo.keybind) == "function" then
                        keybind = buttonInfo.keybind(dialog)
                    else
                        keybind = buttonInfo.keybind
                    end
                elseif buttonInfo.keybind == nil then
                    if i == 1 then
                        keybind = "DIALOG_PRIMARY"
                    else
                        keybind = "DIALOG_NEGATIVE"
                    end
                else
                    hasKeybind = false
                end

                local isButtonEnabled
                if buttonInfo.enabled ~= nil then
                    if type(buttonInfo.enabled) == "function" then
                        isButtonEnabled = buttonInfo.enabled(dialog)
                    else
                        isButtonEnabled = buttonInfo.enabled
                    end
                end

                if isButtonEnabled ~= nil then
                    button:SetEnabled(isButtonEnabled)
                    button:SetKeybindEnabled(hasKeybind and isButtonEnabled)
                else
                    button:SetKeybindEnabled(hasKeybind)
                end
                button:SetKeybind(keybind)

                if buttonInfo.clickSound then
                    button:SetClickSound(buttonInfo.clickSound)
                else
                    if keybind == "DIALOG_NEGATIVE" then
                        button:SetClickSound(SOUNDS.DIALOG_DECLINE)
                    else
                        button:SetClickSound(SOUNDS.DIALOG_ACCEPT)
                    end
                end

                if buttonInfo.requiresTextInput then
                    dialog.requiredTextFields:AddButton(button)
                end
            end
        end
    end

    --Custom Init
    if dialogInfo.customControl or isGamepadDialog then
        RefreshMainText(dialog, dialogInfo, textParams)
        if dialogInfo.setup then
            dialogInfo.setup(dialog, data, textParams)
        end
    else
        local mainText, textControl = RefreshMainText(dialog, dialogInfo, textParams)

        if not mainText then
            return nil
        end

        ZO_Dialogs_SetDialogLoadingIcon(dialog.loadingIcon, textControl, dialogInfo.showLoadingIcon)

        local modalUnderlay = dialog:GetNamedChild("ModalUnderlay")
        if modalUnderlay then
            if dialogInfo.modal == nil or dialogInfo.modal then
                modalUnderlay:SetHidden(false)
            else
                modalUnderlay:SetHidden(true)
            end
        end

        local controlAbove = textControl

        if dialogInfo.editBox then
            local editControl = dialog:GetNamedChild("EditBox")
            local editContainer = dialog:GetNamedChild("Edit")
            local editBoxInfo = dialogInfo.editBox

            editContainer:SetAnchor(TOPLEFT, controlAbove, BOTTOMLEFT, 0, 10)
            editContainer:SetAnchor(TOPRIGHT, controlAbove, BOTTOMRIGHT, 0, 10)
            editContainer:SetHidden(false)

            if editBoxInfo.textType then
                editControl:SetTextType(editBoxInfo.textType)

                if editBoxInfo.specialCharacters then
                    for _, character in pairs(editBoxInfo.specialCharacters) do
                        editControl:AddValidCharacter(character)
                    end
                else
                    editControl:RemoveAllValidCharacters()
                end
            else
                editControl:SetTextType(TEXT_TYPE_ALL)
            end

            if editBoxInfo.maxInputCharacters then
                editControl:SetMaxInputChars(editBoxInfo.maxInputCharacters)
            else
                editControl:SetMaxInputChars(128)
            end

            local defaultText = textParams.editBoxDefaultText or editBoxInfo.defaultText
            if defaultText then
                if type(defaultText) == "number" then
                    defaultText = GetString(defaultText)
                end
                editControl:SetDefaultText(defaultText)
            else
                editControl:SetDefaultText("")
            end

            if textParams.initialEditText then
                editControl:SetText(textParams.initialEditText)
            end

            if editBoxInfo.autoComplete then
                if editControl.autoComplete then
                    editControl.autoComplete:SetEnabled(true)
                else
                    editControl.autoComplete = ZO_AutoComplete:New(editControl)
                end

                editControl.autoComplete:SetIncludeFlags(editBoxInfo.autoComplete.includeFlags)
                editControl.autoComplete:SetExcludeFlags(editBoxInfo.autoComplete.excludeFlags)
                editControl.autoComplete:SetOnlineOnly(editBoxInfo.autoComplete.onlineOnly)
                editControl.autoComplete:SetMaxResults(editBoxInfo.autoComplete.maxResults)
            else
                if editControl.autoComplete then
                    editControl.autoComplete:SetEnabled(false)
                end
            end

            if editBoxInfo.instructions and #editBoxInfo.instructions > 0 then
                if editControl.instructions then
                    editControl.instructions:ClearInstructions()
                    editControl.instructions:AddInstructions(editBoxInfo.instructions)
                else
                    local DEFAULT_TEMPLATE = nil
                    editControl.instructions = ZO_ValidNameInstructions:New(editContainer:GetNamedChild("Instructions"), DEFAULT_TEMPLATE, editBoxInfo.instructions)
                end
                editControl.instructions:Show(dialog.nameEdit)
            elseif editControl.instructions then
                editControl.instructions:ClearInstructions()
                editControl.instructions:Hide()
            end

            if editBoxInfo.validatesText and editBoxInfo.validator then
                editControl.validator = editBoxInfo.validator
            else
                editControl.validator = nil
            end

            if editBoxInfo.matchingString then
                dialog.requiredTextFields:SetMatchingString(editBoxInfo.matchingString)
            end

            if editBoxInfo.selectAll then
                editControl:SelectAll()
            end

            controlAbove = editControl
        end

        local radioButtonContainer = dialog:GetNamedChild("RadioButtonContainer")
        dialog.radioButtonPool:ReleaseAllObjects()
        dialog.radioButtonGroup:Clear()
        radioButtonContainer:SetHidden(true)

        if dialogInfo.radioButtons then
            radioButtonContainer:SetHidden(false)
            radioButtonContainer:SetAnchor(TOPLEFT, controlAbove, BOTTOMLEFT, 0, 15)
            radioButtonContainer:SetAnchor(TOPRIGHT, controlAbove, BOTTOMRIGHT, 0, 15)

            local prev
            for i = 1, #dialogInfo.radioButtons do
                local buttonInfo = dialogInfo.radioButtons[i]
                local radioButton = dialog.radioButtonPool:AcquireObject()
                dialog.radioButtonGroup:Add(radioButton)

                local label = GetControl(radioButton, "Label")
                label:SetText(buttonInfo.text)
                radioButton.data = buttonInfo.data

                if i == 1 then
                    dialog.radioButtonGroup:SetClickedButton(radioButton)
                    radioButton:SetAnchor(TOPLEFT, nil, TOPLEFT, 15, 0)
                else
                    radioButton:SetAnchor(TOPLEFT, prev, BOTTOMLEFT, 0, 10)
                end

                prev = radioButton
            end

            controlAbove = radioButtonContainer
        end

        if dialogInfo.warning then
            warningLabel:SetAnchor(TOPLEFT, controlAbove, BOTTOMLEFT, 0, 15)
            warningLabel:SetAnchor(TOPRIGHT, controlAbove, BOTTOMRIGHT, 0, 15)
            controlAbove = warningLabel
        end

        -- Handle button centering
        local btn1 = dialog:GetNamedChild("Button1")
        local btn2 = dialog:GetNamedChild("Button2")

        if numButtonInfos == 0 then -- Hide both buttons
            btn1:SetHidden(true)
            btn2:SetHidden(true)
        elseif numButtonInfos == 1 then -- Only show one
            btn2:SetHidden(true)

            btn1:ClearAnchors()
            if isGamepad then
                btn1:SetAnchor(TOPLEFT, controlAbove, BOTTOMLEFT, 0, 23)
            else
                btn1:SetAnchor(TOPRIGHT, controlAbove, BOTTOMRIGHT, 0, 23)
            end
        elseif numButtonInfos == 2 then -- Show both
            btn2:ClearAnchors()
            btn1:ClearAnchors()
            if isGamepad then
                btn1:SetAnchor(TOPLEFT, controlAbove, BOTTOMLEFT, 0, 23)
                btn2:SetAnchor(TOPLEFT, btn1, TOPRIGHT, BUTTON_SPACING, 0)
            else
                btn2:SetAnchor(TOPRIGHT, controlAbove, BOTTOMRIGHT, 0, 23)
                btn1:SetAnchor(TOPRIGHT, btn2, TOPLEFT, -BUTTON_SPACING, 0)
            end

        end

        if dialogInfo.callback then
            -- Pass in the id of the dialog being shown for this purpose.
            -- It can (eventually) be used to track this particular dialog instance.
            dialogInfo.callback(dialog.id)
        end
    end

    dialog:SetHandler("OnUpdate", dialogInfo.updateFn)

    g_displayedDialog = {name = name, dialog = dialog }
    ZO_DIALOG_SYNC_OBJECT:Show()
    ZO_DIALOG_SYNC_OBJECT:SetState(name)

    if not isGamepad then
        if(SCENE_MANAGER.RegisterTopLevel) then
            SCENE_MANAGER:RegisterTopLevel(dialog, TOPLEVEL_LOCKS_UI_MODE)
            SCENE_MANAGER:ShowTopLevel(dialog)
        else
            dialog:SetHidden(false)
        end
    else
        ZO_GenericGamepadDialog_Show(dialog)
    end

    -- Append the keybind state index to the dialog so that it knows where its keybinds sit on the keybind stack
    dialog.keybindStateIndex = KEYBIND_STRIP:GetTopKeybindStateIndex()

    dialog.name = name
    dialog:BringWindowToTop()
    if not isGamepadDialog then
        PlaySound(SOUNDS.DIALOG_SHOW)
    end

    --edit controls cant take focus when hidden
    if(dialogInfo.editBox) then
        dialog:GetNamedChild("EditBox"):TakeFocus()
    end

    return dialog
end

function ZO_TwoButtonDialog_OnInitialized(self, id)
    self.id = id
    self.requiredTextFields = ZO_RequiredTextFields:New()
    self.requiredTextFields:AddTextField(GetControl(self, "EditBox"))
    self.radioButtonGroup = ZO_RadioButtonGroup:New()
    self.radioButtonPool = ZO_ControlPool:New("ZO_DialogRadioButton", self:GetNamedChild("RadioButtonContainer"), "RadioButton")
    self.buttonControls = { GetControl(self, "Button1"), GetControl(self, "Button2") }
    self.loadingIcon = GetControl(self, "Loading")
end

function ZO_Dialogs_InitializeDialog(dialog, isGamepad)
    ReanchorDialog(dialog, isGamepad)

    local textControl = dialog:GetNamedChild("Text")
    local button1Control = dialog:GetNamedChild("Button1")
    local button2Control = dialog:GetNamedChild("Button2")
    local buttonExtraText1Control = dialog:GetNamedChild("ButtonExtraText1")
    local buttonExtraText2Control = dialog:GetNamedChild("ButtonExtraText2")
    local editContainer = dialog:GetNamedChild("Edit")
    local editControl = dialog:GetNamedChild("EditBox")
    local warningLabel = dialog:GetNamedChild("WarningText")

    textControl:SetHorizontalAlignment(TEXT_ALIGN_LEFT)
    warningLabel:SetHorizontalAlignment(TEXT_ALIGN_LEFT)
    buttonExtraText1Control:SetHidden(true)
    buttonExtraText2Control:SetHidden(true)
    button1Control:SetState(BSTATE_NORMAL, false)
    button2Control:SetState(BSTATE_NORMAL, false)
    button1Control:SetKeybindEnabled(true)
    button2Control:SetKeybindEnabled(true)
    editContainer:SetHidden(true)
    warningLabel:SetHidden(true)
    editControl:SetText("")
    editControl:LoseFocus()

    if(not g_currencyPool) then
        g_currencyPool = ZO_ControlPool:New("ZO_CurrencyTemplate", dialog, "Currency")
    end
    dialog.buttonCostKeys = {}
    dialog.currencyKey = nil
    dialog.requiredTextFields:ClearButtons()
    dialog.requiredTextFields:SetMatchingString(nil)

    dialog.instanceId = g_curInstanceId
    g_curInstanceId = g_curInstanceId + 1
end

function ZO_Dialogs_ReleaseAllDialogsOfName(name, filterFunction)
    RemoveQueuedDialogs(name, filterFunction)

    local dialog = ZO_Dialogs_FindDialog(name, filterFunction)
    if dialog then
        ZO_Dialogs_ReleaseDialog(dialog)
    end

    ZO_Dialogs_SetDialogQueuePaused(false)
end

function ZO_Dialogs_ReleaseAllDialogsExcept(...)
    RemoveQueuedDialogsExcept(...)

    if g_displayedDialog and not ContainsName(g_displayedDialog.name, ...) then
        ZO_Dialogs_ReleaseDialog(g_displayedDialog.dialog)
    end

    ZO_Dialogs_SetDialogQueuePaused(false)
end

function ZO_Dialogs_ReleaseDialogOnButtonPress(nameOrDialog)
    return ZO_Dialogs_ReleaseDialog(nameOrDialog, RELEASED_FROM_BUTTON_PRESS)
end

function ZO_Dialogs_ReleaseDialog(nameOrDialog, releasedFromButton, filterFunction)
    local dialog = ZO_Dialogs_FindDialog(nameOrDialog, filterFunction)

    if dialog == nil then
        -- This dialog is not currently visible
        return false
    end

    if dialog.hiding then
        return false
    end

    dialog.hiding = true

    if dialog.isGamepad then
        --Gamepad dialog will release when it's finished hiding
        dialog.hideFunction(dialog, releasedFromButton)
    else
        --Keyboard dialogs will hide instantly
        if SCENE_MANAGER.HideTopLevel then
            SCENE_MANAGER:HideTopLevel(dialog)
        else
            dialog:SetHidden(true)
        end
        ZO_CompleteReleaseDialogOnDialogHidden(dialog, releasedFromButton)
    end

    return true
end

function ZO_Dialogs_IsDialogQueuePaused()
    return g_isDialogQueuePaused
end

function ZO_Dialogs_SetDialogQueuePaused(isPaused, exemptionListOrFunction)
    if isPaused ~= g_isDialogQueuePaused then
        g_isDialogQueuePaused = isPaused
        if isPaused then
            g_dialogPauseExemptionsListOrFunction = exemptionListOrFunction
        else
            -- Show next dialog in queue
            local queuedDialog = table.remove(g_dialogQueue, 1)
            if queuedDialog then
                ZO_Dialogs_ShowDialog(unpack(queuedDialog))
            end
            g_dialogPauseExemptionsListOrFunction = nil
        end
    end
end

function ZO_Dialogs_IsDialogExemptFromDialogQueuePause(dialogName, dialogData)
    if type(g_dialogPauseExemptionsListOrFunction) == "table" then
        return ZO_IsElementInNumericallyIndexedTable(g_dialogPauseExemptionsListOrFunction, dialogName)
    end

    if type(g_dialogPauseExemptionsListOrFunction) == "function" then
        return g_dialogPauseExemptionsListOrFunction(dialogName, dialogData)
    end

    return false
end

function ZO_CompleteReleaseDialogOnDialogHidden(dialog, releasedFromButton)
    dialog.hiding = false

    local name = dialog.name
    local dialogInfo = dialog.info

    if not dialogInfo.customControl then
        dialog:SetHandler("OnUpdate", nil)

        for i = 1, NUM_DIALOG_BUTTONS do
            local btn = dialog:GetNamedChild("Button"..i)
            if btn then
                btn.m_callback = nil
                btn.m_noReleaseOnClick = nil
            end

            if dialog.buttonCostKeys and dialog.buttonCostKeys[i] then
                g_currencyPool:ReleaseObject(dialog.buttonCostKeys[i])
                dialog.buttonCostKeys[i] = nil
            end
        end

        if dialog.currencyKey then
            g_currencyPool:ReleaseObject(dialog.currencyKey)
            dialog.currencyKey = nil
        end
    end

    dialog.name = nil

    if ZO_Dialogs_IsShowing(dialog) then
        g_displayedDialog = nil
        ZO_DIALOG_SYNC_OBJECT:SetState(nil)
        ZO_DIALOG_SYNC_OBJECT:Hide()
    end

    if dialogInfo.noChoiceCallback and not releasedFromButton then
        dialogInfo.noChoiceCallback(dialog)
    end

    if next(g_dialogQueue) then
        local currentScene = SCENE_MANAGER:GetCurrentScene()
        local state = currentScene and currentScene:GetState()
        if state == SCENE_HIDING or state == SCENE_HIDDEN or not state then
            ZO_Dialogs_ReleaseAllDialogs(true)
        end
    end

    if dialogInfo.finishedCallback then
        dialogInfo.finishedCallback(dialog)
    end

    local _, queuedDialog = next(g_dialogQueue)
    if queuedDialog then
        if not g_isDialogQueuePaused or ZO_Dialogs_IsDialogExemptFromDialogQueuePause(queuedDialog[QUEUED_DIALOG_INDEX_NAME], queuedDialog[QUEUED_DIALOG_INDEX_DATA]) then
            -- Show next dialog in queue
            table.remove(g_dialogQueue, 1)
            ZO_Dialogs_ShowDialog(unpack(queuedDialog))
            return
        end
    end

    if not ZO_Dialogs_IsShowingDialog() then
        CALLBACK_MANAGER:FireCallbacks("AllDialogsHidden")
    end
end

function ZO_Dialogs_ReleaseAllDialogs(forceAll)
    for _, dialog in ipairs(g_dialogQueue) do
        local dialogInfo = ESO_Dialogs[dialog[QUEUED_DIALOG_INDEX_NAME]]
        if dialogInfo and dialogInfo.removedFromQueueCallback then
            dialogInfo.removedFromQueueCallback(dialog[QUEUED_DIALOG_INDEX_DATA])
        end
    end

    ZO_ClearNumericallyIndexedTable(g_dialogQueue)

    local dialog = GetDisplayedDialog()
    if dialog and (forceAll or not dialog.info.mustChoose) then
        ZO_Dialogs_ReleaseDialog(dialog)
    end

    ZO_Dialogs_SetDialogQueuePaused(false)
end

-- If textTable is nil, the default mainText table (defined in the ESO_Dialogs table) is used
function ZO_Dialogs_UpdateDialogMainText(dialog, textTable, params)
    if dialog then
        if dialog.isGamepad then
            if dialog.info and dialog.headerData then
                local mainTextTable = textTable or dialog.info.mainText
                local mainText = ZO_GetFormattedDialogText(dialog, mainTextTable, params)
                if mainText and mainText ~= "" then
                    ZO_GenericGamepadDialog_RefreshText(dialog, dialog.headerData.titleText, mainText, dialog.warningTextControl:GetText(), dialog.subTextControl:GetText())
                end
            end
        else
            local textControl = dialog:GetNamedChild("Text")
            if textTable then
                dialog.mainText = textTable
            end

            SetDialogTextFormatted(dialog, textControl, dialog.mainText, params)
        end
    end
end

function ZO_Dialogs_UpdateDialogTitleText(dialog, textTable, params)
    if dialog then
        local titleControl = dialog:GetNamedChild("Title")
        if textTable then
            dialog.title = textTable
        end
        
        SetDialogTextFormatted(dialog, titleControl, dialog.title, params)
    end
end

function ZO_Dialogs_UpdateDialogWarningText(dialog, textTable, params)
    if dialog then
        if dialog.isGamepad then
            if dialog.info and dialog.headerData then
                textTable = textTable or dialog.info.warning

                local warningText = ZO_GetFormattedDialogText(dialog, textTable, params)
                if warningText and warningText ~= "" then
                    ZO_GenericGamepadDialog_RefreshText(dialog, dialog.headerData.titleText, dialog.mainTextControl:GetText(), warningText, dialog.subTextControl:GetText())
                end
            end
        else
            local warningLabel = dialog:GetNamedChild("WarningText")
            if textTable then
                dialog.warning = textTable
            end
        
            SetDialogTextFormatted(dialog, warningLabel, dialog.warning, params)
        end
    end
end

function ZO_Dialogs_GetEditBoxText(dialog)
    if(dialog) then
        local editControl = dialog:GetNamedChild("EditBox")

        if not editControl:IsHidden() then
            return editControl:GetText()
        end
    end
    
    return nil
end

function ZO_Dialogs_GetSelectedRadioButtonData(dialog)
    local clickedButton = dialog.radioButtonGroup:GetClickedButton()
    if(clickedButton) then
        return clickedButton.data
    end
end

function ZO_Dialogs_UpdateButtonVisibilityAndEnabledState(dialog)
    local buttonInfos = dialog.info.buttons
    local numButtonInfos = buttonInfos and #buttonInfos or 0
    dialog.numButtons = numButtonInfos
    if numButtonInfos > 0 and not dialog.isGamepad then
        for i = 1, numButtonInfos do
            local buttonInfo = buttonInfos[i]
            local button = GetButtonControl(dialog, i)

            local buttonVisible = true
            if buttonInfo.visible ~= nil then
                if type(buttonInfo.visible) == "function" then
                    buttonVisible = buttonInfo.visible(dialog)
                else
                    buttonVisible = buttonInfo.visible
                end
            end

            if not buttonVisible then
                button:SetHidden(true)
                button:SetKeybindEnabled(false)
            else
                local isButtonEnabled = true
                if buttonInfo.enabled ~= nil then
                    if type(buttonInfo.enabled) == "function" then
                        isButtonEnabled = buttonInfo.enabled(dialog)
                    else
                        isButtonEnabled = buttonInfo.enabled
                    end
                end

                local hasKeybind = button:GetKeybind() ~= nil
                button:SetHidden(false)
                button:SetEnabled(isButtonEnabled)
                button:SetKeybindEnabled(hasKeybind and isButtonEnabled)
            end
        end
    end
end

-- Activate or deactivate a button...use BSTATE_NORMAL to activate and BSTATE_DISABLED to deactivate
function ZO_Dialogs_UpdateButtonState(dialog, buttonNumber, buttonState)
    if(dialog and buttonNumber) then
        local buttonControl = dialog:GetNamedChild("Button"..buttonNumber)
        local lockButton = false
        if(buttonState == BSTATE_DISABLED) then
            lockButton = true
        end
        buttonControl:SetState(buttonState, lockButton)
    end
end

-- Update the text on a button itself
function ZO_Dialogs_UpdateButtonText(dialog, buttonNumber, text)
    if(dialog and buttonNumber) then
        local buttonControl = dialog:GetNamedChild("Button"..buttonNumber)
        if(text) then
            if(type(text) == "number") then
                text = GetString(text)
            end
            buttonControl:SetText(text)
        end
    end
end

-- Update the text underneath a button...if textTable is nil, this extra text control is hidden
function ZO_Dialogs_UpdateButtonExtraText(dialog, buttonNumber, textTable, params)
    if dialog and buttonNumber then
        local textControl = dialog:GetNamedChild("ButtonExtraText"..buttonNumber)
        if textTable then
            SetDialogTextFormatted(dialog, textControl, textTable, params)
            textControl:SetHidden(false)
        else
            textControl:SetHidden(true)
        end
    end
end

-- Update the currency control underneath a button
function ZO_Dialogs_UpdateButtonCost(dialog, buttonNumber, cost)
    if dialog then
        local buttonCostsShown = 0
        for i = 1,NUM_DIALOG_BUTTONS do
            if dialog.buttonCostKeys[i] then
                buttonCostsShown = buttonCostsShown + 1
            end
        end

        if cost then
            local textControl = dialog:GetNamedChild("ButtonExtraText"..buttonNumber)
            local buttonControl = dialog:GetNamedChild("Button"..buttonNumber)

            local currencyControl
            local key = dialog.buttonCostKeys[buttonNumber]
            if key then
                currencyControl = g_currencyPool:AcquireObject(key)
            else
                local newCurrencyControl, newCurrencyKey = g_currencyPool:AcquireObject()
                newCurrencyControl:SetParent(dialog)
                dialog.buttonCostKeys[buttonNumber] = newCurrencyKey
                currencyControl = newCurrencyControl
            end

            ZO_CurrencyControl_SetSimpleCurrency(currencyControl, CURT_MONEY, cost, nil, CURRENCY_DONT_SHOW_ALL)
            local visibleCurrencyWidth = currencyControl:GetWidth()
            if textControl:IsHidden() then
                currencyControl:SetAnchor(TOPLEFT, buttonControl, BOTTOM, -visibleCurrencyWidth / 2, 5)
            else
                currencyControl:SetAnchor(TOPLEFT, textControl, BOTTOM, -visibleCurrencyWidth / 2, 5)
            end

            currencyControl:SetHidden(false)
        else
            local key = dialog.buttonCostKeys[buttonNumber]
            if key then
                g_currencyPool:ReleaseObject(key)
                dialog.buttonCostKeys[buttonNumber] = nil
            end
        end
    end
end

function ZO_Dialogs_IsDialogRegistered(name)
    local dialogInfo = ESO_Dialogs[name]
    return dialogInfo and (type(dialogInfo) == "table")
end

function ZO_Dialogs_RegisterCustomDialog(name, info)
    ESO_Dialogs[name] = info
end

function ZO_Dialogs_CloseKeybindPressed()
    local dialog = GetDisplayedDialog()
    if dialog then
        if not dialog.info.mustChoose then
            if not ZO_Dialogs_ReleaseDialog(dialog.name, not RELEASED_FROM_BUTTON_PRESS) then
                dialog:SetHidden(true)
            end
            if dialog.info.hideSound then
                PlaySound(dialog.info.hideSound)
            else
                if not dialog.isGamepad then
                    PlaySound(SOUNDS.DIALOG_HIDE)
                end
            end

            if dialog.isGamepad then
                SCENE_MANAGER:RequestShowLeaderBaseScene()
            end
        end
    end
end

function ZO_Dialogs_HandleButtonForKeybind(dialog, keybind)
    local handledButton = false
    for i = 1, dialog.numButtons do
        local btn = GetButtonControl(dialog, i)
        if(btn ~= nil and btn:IsEnabled() and btn:GetKeybind() == keybind) then
            btn:OnClicked()
            handledButton = true
            break
        end
    end

    return handledButton
end

function ZO_Dialogs_ButtonKeybindPressed(keybind)
    local dialog = GetDisplayedDialog()
    if(dialog) then
        local handledButton = ZO_Dialogs_HandleButtonForKeybind(dialog, keybind)

        if(not handledButton and IsInGamepadPreferredMode()) then
            if(ZO_KeybindStrip_HandleKeybindDown(keybind)) then
                if(not dialog.info.blockDialogReleaseOnPress) then
                    ZO_Dialogs_ReleaseDialogOnButtonPress(dialog.name)
                end
            end
        end
    end
end

function ZO_Dialogs_ButtonKeybindReleased(keybind)
    local dialog = GetDisplayedDialog()
    if(dialog) then
        local handledButton = ZO_Dialogs_HandleButtonForKeybind(dialog, keybind)

        if(not handledButton) then
            ZO_KeybindStrip_HandleKeybindUp(keybind) 
        end
    end
end

function ZO_SharedDialogButton_OnInitialized(self)
    ZO_KeybindButtonTemplate_OnInitialized(self)
    self:SetCallback(HandleCallback)
end

function ZO_CustomDialogButton_OnInitialized(self)
    ZO_SharedDialogButton_OnInitialized(self)

    local parent = self:GetParent()
    local maxButtonIndex
    local maxButton
    for i = 1, parent:GetNumChildren() do
        local child = parent:GetChild(i)
        local customButtonIndex = child.customButtonIndex
        if(customButtonIndex ~= nil) then
            if(maxButtonIndex == nil or customButtonIndex > maxButtonIndex) then
                maxButtonIndex = customButtonIndex
                maxButton = child
            end
        end
    end

    if(maxButton) then
        self:SetAnchor(TOPRIGHT, maxButton, TOPLEFT, -BUTTON_SPACING, 0)
    else
        self:SetAnchor(BOTTOMRIGHT, nil, BOTTOMRIGHT, -25, -15)
    end

    if(maxButtonIndex) then
        self.customButtonIndex = maxButtonIndex + 1
    else
        self.customButtonIndex = 1
    end
end

function ZO_DialogButton_OnInitialized(self)
    ZO_ChromaKeybindButtonTemplate_OnInitialized(self)
    self:SetCallback(HandleCallback)
end

function ZO_TwoButtonDialogEditBox_OnTextChanged(control)
    if control.instructions then
        if control.validator then
            local DEFAULT_ANCHOR_CONTROL = nil
            local violations = {control.validator(control:GetText())}
            control.instructions:Show(DEFAULT_ANCHOR_CONTROL, violations)
        else
            control.instructions:Hide()
        end
    end
end

function ZO_TwoButtonDialogEditBox_OnFocusGained(control)
    if control.instructions then
        if control.validator then
            local DEFAULT_ANCHOR_CONTROL = nil
            local violations = {control.validator(control:GetText())}
            control.instructions:Show(DEFAULT_ANCHOR_CONTROL, violations)
        end
    end
end

function ZO_TwoButtonDialogEditBox_OnFocusLost(control)
    if control.instructions and control.instructions:HasRules() then
        if control.validator then
            local violations = {control.validator(control:GetText())}
            if #violations == 0 then
                control.instructions:Hide()
            end
        end
    end
end

EVENT_MANAGER:RegisterForEvent("ZO_Dialog", EVENT_GUI_UNLOADING, function()
    if ZO_Dialogs_IsShowingDialog() then
        ZO_DIALOG_SYNC_OBJECT:Hide()
    end
end)

if EVENT_PLAYER_ACTIVATED ~= nil then
    -- This event only exists in AllIngame GUIs, will not exist in Pregame.
    EVENT_MANAGER:RegisterForEvent("ZO_Dialog", EVENT_PLAYER_ACTIVATED, function()
        if not ZO_Dialogs_IsShowingDialog() then
            -- Catch-all to ensure the dialog action layer was correctly removed
            -- in case the remove action layer failed due to being in the middle of loading
            RemoveActionLayerByName(GetString(SI_KEYBINDINGS_LAYER_DIALOG))
        end
    end)
end