ZO_GAMEPAD_HEADER_TABBAR_CREATE = true
ZO_GAMEPAD_HEADER_TABBAR_DONT_CREATE = false

ZO_GAMEPAD_HEADER_LAYOUTS =
{
--[[
    The key/value pairs are arranged beneath the title row as follows:

    Screen Header (left pane):
        data1Header             data1
        data2Header             data2
        data3Header             data3
        data4Header             data4

    Content Header (right pane):
        data1Header             data1   data3Header             data3
        data2Header             data2   data4Header             data4
--]]
    DATA_PAIRS_SEPARATE = 1,

--[[
    The key/value pairs are arranged beneath the title row as follows:

    Screen Header (left pane):
        data1Header data1       data2Header data2
        data3Header data3       data4Header data4

    Content Header (right pane):
        data1Header data1       data2Header data2   data3Header data3       data4Header data4
--]]
    DATA_PAIRS_TOGETHER = 2,

--[[
    The key/value pairs are arranged beneath the title row as follows:

    Screen Header (left pane):
        Not defined.  Don't use this - it's a special case for the content header.

    Content Header (right pane):
        data1Header data1 data2Header data2 data3Header data3 data4Header data4

        If a data section is not provided, it will be disregarded when it comes to anchoring (data1 will anchor to data3 if there's no data2, etc.)
--]]
    CONTENT_HEADER_DATA_PAIRS_LINKED = 3,
}

ZO_GAMEPAD_HEADER_CONTROLS =
{
    TABBAR          = 1,
    TITLE           = 2,
    CENTER_BASELINE = 3,
    TITLE_BASELINE  = 4,
    DIVIDER_SIMPLE  = 5,
    DIVIDER_PIPPED  = 6,
    DATA1           = 7,
    DATA1HEADER     = 8,
    DATA2           = 9,
    DATA2HEADER     = 10,
    DATA3           = 11,
    DATA3HEADER     = 12,
    DATA4           = 13,
    DATA4HEADER     = 14,
    MESSAGE         = 15,
    SUBTITLE        = 16,
}

-- Alias the control names to make the code less verbose and more readable.
local TABBAR            = ZO_GAMEPAD_HEADER_CONTROLS.TABBAR
local TITLE             = ZO_GAMEPAD_HEADER_CONTROLS.TITLE
local SUBTITLE          = ZO_GAMEPAD_HEADER_CONTROLS.SUBTITLE
local CENTER_BASELINE   = ZO_GAMEPAD_HEADER_CONTROLS.CENTER_BASELINE
local TITLE_BASELINE    = ZO_GAMEPAD_HEADER_CONTROLS.TITLE_BASELINE
local DIVIDER_SIMPLE    = ZO_GAMEPAD_HEADER_CONTROLS.DIVIDER_SIMPLE
local DIVIDER_PIPPED    = ZO_GAMEPAD_HEADER_CONTROLS.DIVIDER_PIPPED
local DATA1             = ZO_GAMEPAD_HEADER_CONTROLS.DATA1
local DATA1HEADER       = ZO_GAMEPAD_HEADER_CONTROLS.DATA1HEADER
local DATA2             = ZO_GAMEPAD_HEADER_CONTROLS.DATA2
local DATA2HEADER       = ZO_GAMEPAD_HEADER_CONTROLS.DATA2HEADER
local DATA3             = ZO_GAMEPAD_HEADER_CONTROLS.DATA3
local DATA3HEADER       = ZO_GAMEPAD_HEADER_CONTROLS.DATA3HEADER
local DATA4             = ZO_GAMEPAD_HEADER_CONTROLS.DATA4
local DATA4HEADER       = ZO_GAMEPAD_HEADER_CONTROLS.DATA4HEADER
local MESSAGE           = ZO_GAMEPAD_HEADER_CONTROLS.MESSAGE

local DEFAULT_LAYOUT        = ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_SEPARATE
local FIRST_DATA_CONTROL    = DATA1
local LAST_DATA_CONTROL     = MESSAGE

-- Because these consts are created before the xml is loaded, we don't have an effecient way to track the anticipated height of the info labels
-- We need to track the disparity to line up the bottoms, so we just grabbed these numbers ahead of time
-- TODO: Revisit this and come up with a better way to anchor dynamically
local GENERIC_HEADER_INFO_LABEL_HEIGHT = 33
local GENERIC_HEADER_INFO_DATA_HEIGHT = 50
local GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY = 4 -- This is used to align the baselines of the headers and their texts; will need to update if fonts change
local ROW_OFFSET_Y = GENERIC_HEADER_INFO_LABEL_HEIGHT + 10
local DATA_OFFSET_X = 5
local HEADER_OFFSET_X = 29

ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y = ZO_GAMEPAD_CONTENT_DIVIDER_INFO_PADDING_Y + GENERIC_HEADER_INFO_LABEL_HEIGHT

-- The Anchor class simply wraps a ZO_Anchor object with a target id, which we can later resolve into an actual control.
-- This allows us to specify all anchor data at file scope and resolve the target controls only when needed.
local Anchor = ZO_Object:Subclass()

function Anchor:New(pointOnMe, targetId, pointOnTarget, offsetX, offsetY)
    local object = ZO_Object.New(self)
    object.targetId = targetId
    object.anchor = ZO_Anchor:New(pointOnMe, nil, pointOnTarget, offsetX, offsetY)
    return object
end

local SCREEN_HEADER_ANCHORS =
{
    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_SEPARATE] =
    {
        [DATA1HEADER]   = { Anchor:New(BOTTOMLEFT, DIVIDER_SIMPLE, BOTTOMLEFT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y),
                            Anchor:New(BOTTOMRIGHT, DIVIDER_SIMPLE, BOTTOMRIGHT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y) },
        [DATA1]         = { Anchor:New(BOTTOMRIGHT, DATA1HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA2HEADER]   = { Anchor:New(BOTTOMLEFT, DATA1HEADER, BOTTOMLEFT, 0, ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA1HEADER, BOTTOMRIGHT, 0, ROW_OFFSET_Y) },
        [DATA2]         = { Anchor:New(BOTTOMRIGHT, DATA2HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA3HEADER]   = { Anchor:New(BOTTOMLEFT, DATA2HEADER, BOTTOMLEFT, 0, ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA2HEADER, BOTTOMRIGHT, 0, ROW_OFFSET_Y) },
        [DATA3]         = { Anchor:New(BOTTOMRIGHT, DATA3HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA4HEADER]   = { Anchor:New(BOTTOMLEFT, DATA3HEADER, BOTTOMLEFT, 0, ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA3HEADER, BOTTOMRIGHT, 0, ROW_OFFSET_Y) },
        [DATA4]         = { Anchor:New(BOTTOMRIGHT, DATA4HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [MESSAGE]       = { Anchor:New(TOPLEFT, DIVIDER_SIMPLE, BOTTOMLEFT, 0, ZO_GAMEPAD_CONTENT_DIVIDER_INFO_PADDING_Y),
                            Anchor:New(TOPRIGHT, DIVIDER_SIMPLE, BOTTOMRIGHT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y) },
    },

    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_TOGETHER] =
    {
        [DATA1HEADER]   = { Anchor:New(BOTTOMLEFT, DIVIDER_SIMPLE, BOTTOMLEFT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y) },
        [DATA1]         = { Anchor:New(BOTTOMLEFT, DATA1HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA2]         = { Anchor:New(BOTTOMRIGHT, DIVIDER_SIMPLE, BOTTOMRIGHT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y + GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
        [DATA2HEADER]   = { Anchor:New(BOTTOMRIGHT, DATA2, BOTTOMLEFT, -DATA_OFFSET_X, -GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA3HEADER]   = { Anchor:New(BOTTOMLEFT, DATA1HEADER, BOTTOMLEFT, 0, ROW_OFFSET_Y) },
        [DATA3]         = { Anchor:New(BOTTOMLEFT, DATA3HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA4]         = { Anchor:New(BOTTOMRIGHT, DATA2, BOTTOMRIGHT, 0, ROW_OFFSET_Y + GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
        [DATA4HEADER]   = { Anchor:New(BOTTOMRIGHT, DATA4, BOTTOMLEFT, -DATA_OFFSET_X, -GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [MESSAGE]       = { Anchor:New(TOPLEFT, DIVIDER_SIMPLE, BOTTOMLEFT, 0, ZO_GAMEPAD_CONTENT_DIVIDER_INFO_PADDING_Y),
                            Anchor:New(TOPRIGHT, DIVIDER_SIMPLE, BOTTOMRIGHT, 0, ZO_GAMEPAD_CONTENT_DIVIDER_INFO_PADDING_Y) },
    },
}

-- The overlap anchors are used instead of the normal anchors in cases where the data label's text overlaps its corresponding header label.
local DATA_OVERLAP_ROW_OFFSET_Y = 40
local HEADER_OVERLAP_ROW_OFFSET_Y = 31

local SCREEN_HEADER_OVERLAP_ANCHORS = 
{
    -- Anchors header content as
    -- DATA[N]HEADER
    -- DATA[N]
    -- DATA[N+1]HEADER...
    -- if DATA[N]HEADER and DATA[N] overlap each other
    
    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_SEPARATE] =
    {
        [DATA1]         = { Anchor:New(BOTTOMLEFT, DATA1HEADER, BOTTOMLEFT, 0, DATA_OVERLAP_ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA1HEADER, BOTTOMRIGHT, 0, DATA_OVERLAP_ROW_OFFSET_Y) },

        [DATA2HEADER]   = { Anchor:New(BOTTOMLEFT, DATA1, BOTTOMLEFT, 0, HEADER_OVERLAP_ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA1, BOTTOMRIGHT, 0, HEADER_OVERLAP_ROW_OFFSET_Y) },
        [DATA2]         = { Anchor:New(BOTTOMLEFT, DATA2HEADER, BOTTOMLEFT, 0, DATA_OVERLAP_ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA2HEADER, BOTTOMRIGHT, 0, DATA_OVERLAP_ROW_OFFSET_Y) },

        [DATA3HEADER]   = { Anchor:New(BOTTOMLEFT, DATA2, BOTTOMLEFT, 0, HEADER_OVERLAP_ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA2, BOTTOMRIGHT, 0, HEADER_OVERLAP_ROW_OFFSET_Y) },
        [DATA3]         = { Anchor:New(BOTTOMLEFT, DATA3HEADER, BOTTOMLEFT, 0, DATA_OVERLAP_ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA3HEADER, BOTTOMRIGHT, 0, DATA_OVERLAP_ROW_OFFSET_Y) },

        [DATA4HEADER]   = { Anchor:New(BOTTOMLEFT, DATA3, BOTTOMLEFT, 0, HEADER_OVERLAP_ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA3, BOTTOMRIGHT, 0, HEADER_OVERLAP_ROW_OFFSET_Y) },
        [DATA4]         = { Anchor:New(BOTTOMLEFT, DATA4HEADER, BOTTOMLEFT, 0, DATA_OVERLAP_ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA4HEADER, BOTTOMRIGHT, 0, DATA_OVERLAP_ROW_OFFSET_Y) },
    },
}

local CONTENT_HEADER_ANCHORS =
{
    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_SEPARATE] =
    {
        [DATA1HEADER]   = { Anchor:New(BOTTOMLEFT, DIVIDER_SIMPLE, BOTTOMLEFT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y),
                            Anchor:New(BOTTOMRIGHT, CENTER_BASELINE, BOTTOMLEFT) },
        [DATA1]         = { Anchor:New(BOTTOMRIGHT, DATA1HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA2HEADER]   = { Anchor:New(BOTTOMLEFT, DATA1HEADER, BOTTOMLEFT, 0, ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA1HEADER, BOTTOMRIGHT, 0, ROW_OFFSET_Y) },
        [DATA2]         = { Anchor:New(BOTTOMRIGHT, DATA2HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA3HEADER]   = { Anchor:New(BOTTOMLEFT, CENTER_BASELINE, BOTTOMRIGHT),
                            Anchor:New(BOTTOMRIGHT, DIVIDER_SIMPLE, BOTTOMRIGHT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y) },
        [DATA3]         = { Anchor:New(BOTTOMRIGHT, DATA3HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA4HEADER]   = { Anchor:New(BOTTOMLEFT, DATA3HEADER, BOTTOMLEFT, 0, ROW_OFFSET_Y),
                            Anchor:New(BOTTOMRIGHT, DATA3HEADER, BOTTOMRIGHT, 0, ROW_OFFSET_Y) },
        [DATA4]         = { Anchor:New(BOTTOMRIGHT, DATA4HEADER, BOTTOMRIGHT, 0, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
    },

    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_TOGETHER] =
    {
        [DATA1HEADER]   = { Anchor:New(BOTTOMLEFT, DIVIDER_SIMPLE, BOTTOMLEFT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y) },
        [DATA1]         = { Anchor:New(BOTTOMLEFT, DATA1HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA2]         = { Anchor:New(BOTTOMRIGHT, CENTER_BASELINE, BOTTOMLEFT, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
        [DATA2HEADER]   = { Anchor:New(BOTTOMRIGHT, DATA2, BOTTOMLEFT, -DATA_OFFSET_X, -GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA3HEADER]   = { Anchor:New(BOTTOMLEFT, CENTER_BASELINE, BOTTOMRIGHT) },
        [DATA3]         = { Anchor:New(BOTTOMLEFT, DATA3HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA4]         = { Anchor:New(BOTTOMRIGHT, DIVIDER_SIMPLE, BOTTOMRIGHT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y + GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
        [DATA4HEADER]   = { Anchor:New(BOTTOMRIGHT, DATA4, BOTTOMLEFT, -DATA_OFFSET_X, -GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
    },

    [ZO_GAMEPAD_HEADER_LAYOUTS.CONTENT_HEADER_DATA_PAIRS_LINKED] =
    {
        [DATA1HEADER]   = { Anchor:New(BOTTOMLEFT, DIVIDER_SIMPLE, BOTTOMLEFT, 0, ZO_GAMEPAD_CONTENT_HEADER_DIVIDER_INFO_BOTTOM_PADDING_Y) },
        [DATA1]         = { Anchor:New(BOTTOMLEFT, DATA1HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA2HEADER]   = { Anchor:New(BOTTOMLEFT, DATA1, BOTTOMRIGHT, HEADER_OFFSET_X, -GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
        [DATA2]         = { Anchor:New(BOTTOMLEFT, DATA2HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA3HEADER]   = { Anchor:New(BOTTOMLEFT, DATA2, BOTTOMRIGHT, HEADER_OFFSET_X, -GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
        [DATA3]         = { Anchor:New(BOTTOMLEFT, DATA3HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },

        [DATA4HEADER]   = { Anchor:New(BOTTOMLEFT, DATA3, BOTTOMRIGHT, HEADER_OFFSET_X, -GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY) },
        [DATA4]         = { Anchor:New(BOTTOMLEFT, DATA4HEADER, BOTTOMRIGHT, DATA_OFFSET_X, GENERIC_HEADER_INFO_FONT_HEIGHT_DISPARITY)},
    },
}

-- These targets are only applicable for the SCREEN header.
local MESSAGE_ANCHOR_TARGETS =
{
    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_SEPARATE] =
    {
        {DATA1HEADER, DATA1},
        {DATA2HEADER, DATA2},
        {DATA3HEADER, DATA3},
        {DATA4HEADER, DATA4},
    },

    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_TOGETHER] =
    {
        {DATA1HEADER, DATA1, DATA2HEADER, DATA2},
        {DATA3HEADER, DATA3, DATA4HEADER, DATA4},
    }
}

local function TabBar_Setup(control, data, selected, selectedDuringRebuild, enabled, activated)
    -- TODO: Switch the following code over to use ZO_SharedGamepadEntry_OnSetup.
    if data.canSelect == nil then
        data.canSelect = true
    end
    ZO_GamepadMenuHeaderTemplate_Setup(control, data, selected, selectedDuringRebuild, enabled, activated)
    SetDefaultColorOnLabel(control.text, selected)
end

function ZO_GamepadGenericHeader_Initialize(control, createTabBar, layout)
    control.controls =
    {
        [TABBAR]            = control:GetNamedChild("TabBar"),
        [TITLE]             = control:GetNamedChild("TitleContainer"):GetNamedChild("Title"),
        [SUBTITLE]          = control:GetNamedChild("TitleContainer"):GetNamedChild("SubTitle"),
        [CENTER_BASELINE]   = control:GetNamedChild("CenterAnchor"),
        [TITLE_BASELINE]    = control:GetNamedChild("TitleContainer"),
        [DIVIDER_SIMPLE]    = control:GetNamedChild("DividerSimple"),
        [DIVIDER_PIPPED]    = control:GetNamedChild("DividerPipped"),
        [DATA1]             = control:GetNamedChild("Data1"),
        [DATA1HEADER]       = control:GetNamedChild("Data1Header"),
        [DATA2]             = control:GetNamedChild("Data2"),
        [DATA2HEADER]       = control:GetNamedChild("Data2Header"),
        [DATA3]             = control:GetNamedChild("Data3"),
        [DATA3HEADER]       = control:GetNamedChild("Data3Header"),
        [DATA4]             = control:GetNamedChild("Data4"),
        [DATA4HEADER]       = control:GetNamedChild("Data4Header"),
        [MESSAGE]           = control:GetNamedChild("Message"),
    }

    if createTabBar == ZO_GAMEPAD_HEADER_TABBAR_CREATE then
        local tabBarControl = control.controls[TABBAR]
        local dividerSimpleControl = control.controls[DIVIDER_SIMPLE]
        local dividerPippedControl = control.controls[DIVIDER_PIPPED]

        tabBarControl:SetHidden(false)
        dividerPippedControl:SetHidden(false)
        dividerSimpleControl:SetHidden(true)

        control.tabBar = ZO_GamepadTabBarScrollList:New(tabBarControl, dividerPippedControl:GetNamedChild("LeftIcon"), dividerPippedControl:GetNamedChild("RightIcon"))
        control.tabBar:AddDataTemplate("ZO_GamepadTabBarTemplate", TabBar_Setup, ZO_GamepadMenuEntryTemplateParametricListFunction)
    end

    ZO_GamepadGenericHeader_SetDataLayout(control, layout or DEFAULT_LAYOUT)
end

function ZO_GamepadGenericHeader_AddTabBarTemplate(control, template)
    if control.tabBar then
        control.tabBar:AddDataTemplate(template, TabBar_Setup, ZO_GamepadMenuEntryTemplateParametricListFunction)
    end
end

function ZO_GamepadGenericHeader_GetChildControl(control, controlId)
    return control.controls[controlId]
end

function ZO_GamepadGenericHeader_SetHeaderFocusControl(control, headerFocusControl, headerFocus)
    control.headerFocusControl = headerFocusControl
    control.headerFocus = headerFocus
end

local function ApplyAnchorToControl(control, anchor, targets)
    local target = anchor.targetId and targets[anchor.targetId] or nil
    anchor.anchor:SetTarget(target)
    anchor.anchor:AddToControl(control)
end

local function IsScreenHeader(controls)
    return not controls[CENTER_BASELINE]
end

local function ApplyAnchorSetToControl(dataControl, anchorSet, controls)
    dataControl:ClearAnchors()
    ApplyAnchorToControl(dataControl, anchorSet[1], controls)
    if anchorSet[2] then
        ApplyAnchorToControl(dataControl, anchorSet[2], controls)
    end
end

-- Note: ZO_GamepadGenericHeader_RefreshData must be called after changing the layout.
function ZO_GamepadGenericHeader_SetDataLayout(control, layout)
    if control.layout == layout then
        return
    end

    local controls = control.controls

    local anchorSets = IsScreenHeader(controls) and SCREEN_HEADER_ANCHORS[layout] or CONTENT_HEADER_ANCHORS[layout]
    assert(anchorSets ~= nil)

    for k, anchors in pairs(anchorSets) do
        local dataControl = controls[k]
        ApplyAnchorSetToControl(dataControl, anchors, controls)
    end

    control.layout = layout
end

local g_useHeaderOverlapAnchors = false

local function ReflowScreenHeaderDataPairSeparateIfNecessary(parentControl, dataControlIndex, dataHeaderControlIndex)
    local overlapAnchors = SCREEN_HEADER_OVERLAP_ANCHORS[parentControl.layout]

    if not overlapAnchors then
        return
    end

    local controls = parentControl.controls

    local dataControl = controls[dataControlIndex]
    local dataHeaderControl = controls[dataHeaderControlIndex]

    if g_useHeaderOverlapAnchors then
        if dataHeaderControl then
            local anchorSet = overlapAnchors[dataHeaderControlIndex]

            if anchorSet then
                ApplyAnchorSetToControl(dataHeaderControl, anchorSet, controls)
            end
        end
        g_useHeaderOverlapAnchors = false
    end

    if dataControl and dataHeaderControl then
        if dataControl:GetLeft() < dataHeaderControl:GetLeft() + dataHeaderControl:GetTextWidth() then
            local anchorSet = overlapAnchors[dataControlIndex]
            if anchorSet then
                ApplyAnchorSetToControl(dataControl, anchorSet, controls)
                g_useHeaderOverlapAnchors = true
            end
        end
    end
end

local function ScreenHeaderDataPairsSeparateReflow(control)
    control.layout = nil
    ZO_GamepadGenericHeader_SetDataLayout(control, ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_SEPARATE)
    g_useHeaderOverlapAnchors = false

    ReflowScreenHeaderDataPairSeparateIfNecessary(control, DATA1, DATA1HEADER)
    ReflowScreenHeaderDataPairSeparateIfNecessary(control, DATA2, DATA2HEADER)
    ReflowScreenHeaderDataPairSeparateIfNecessary(control, DATA3, DATA3HEADER)
    ReflowScreenHeaderDataPairSeparateIfNecessary(control, DATA4, DATA4HEADER)
end

local g_currentBottomLeftHeader = DATA1HEADER

local function ReflowContentHeaderDataPairIfNecessary(parentControl, dataControlIndex, dataHeaderControlIndex)
    local controls = parentControl.controls

    local dataControl = controls[dataControlIndex]
    if dataControl and dataControl:GetRight() > parentControl:GetRight() then
        local dataHeaderControl = controls[dataHeaderControlIndex]
        dataHeaderControl:ClearAnchors()
        dataHeaderControl:SetAnchor(BOTTOMLEFT, controls[g_currentBottomLeftHeader], BOTTOMLEFT, 0, ROW_OFFSET_Y)
        g_currentBottomLeftHeader = dataHeaderControlIndex
    end
end

local function ContentHeaderDataPairsLinkedReflow(control)
    control.layout = nil
    ZO_GamepadGenericHeader_SetDataLayout(control, ZO_GAMEPAD_HEADER_LAYOUTS.CONTENT_HEADER_DATA_PAIRS_LINKED)
    g_currentBottomLeftHeader = DATA1HEADER    -- Reset the bottom left header value

    ReflowContentHeaderDataPairIfNecessary(control, DATA2, DATA2HEADER)
    ReflowContentHeaderDataPairIfNecessary(control, DATA3, DATA3HEADER)
    ReflowContentHeaderDataPairIfNecessary(control, DATA4, DATA4HEADER)
end

local SCREEN_HEADER_REFLOW_FUNCS =
{
    [ZO_GAMEPAD_HEADER_LAYOUTS.DATA_PAIRS_SEPARATE] = ScreenHeaderDataPairsSeparateReflow,
}

local CONTENT_HEADER_REFLOW_FUNCS =
{
    [ZO_GAMEPAD_HEADER_LAYOUTS.CONTENT_HEADER_DATA_PAIRS_LINKED] = ContentHeaderDataPairsLinkedReflow,
}

local function ReflowLayout(control)
    local reflowFunc = IsScreenHeader(control.controls) and SCREEN_HEADER_REFLOW_FUNCS[control.layout] or CONTENT_HEADER_REFLOW_FUNCS[control.layout]
    if reflowFunc then
        reflowFunc(control)
    end
end

local function ProcessData(control, data)
    if control == nil then
        return false
    end

    if type(data) == "function" then
        data = data(control)
    end

    if type(data) == "string" or type(data) == "number" then
        control:SetText(data)
    end

    control:SetHidden(not data)
    return data ~= nil
end

local function GetProcessedNarrationText(control, data)
    if control == nil or data == nil then
        return ""
    end

    if type(data) == "function" then
        data = data(control)
        if data == nil then
            return ""
        end
    end

    if type(data) == "string" then
        return data
    elseif type(data) == "number" then
        return tostring(data)
    else
        internalassert(false, "Unsupported data type. A custom narration function will need to be set for this control that returns a string or number.")
    end

    return ""
end

local function SetAnchorOffsetY(control, index, offsetY)
    local isValid, point, relTo, relPoint, offsetX = control:GetAnchor(index)
    if isValid then
        control:SetAnchor(point, relTo, relPoint, offsetX, offsetY)
    end
end

local function AdjustHeaderFocusControlAnchors(control, refreshResults)
    if control.headerFocusControl then
        -- Test each anchor target set to see if any one of the controls in that set is in use.
        -- The idea here is to anchor the message control to the last label/data row that is in use.
        local anchorTargetSets = MESSAGE_ANCHOR_TARGETS[control.layout]
        local previousTarget
        for _, anchorTargets in ipairs(anchorTargetSets) do
            for _, target in ipairs(anchorTargets) do
                if refreshResults[target] ~= false then
                    previousTarget = target
                    break
                end
            end
        end

        local anchorToControl
        if previousTarget then
            anchorToControl = control.controls[previousTarget]
        elseif control.tabBar and control.tabBar.control then
            -- TabBar control does not take the height of the pipped divider into account but is used
            -- to determine if a pipped divider is showing, use the pipped divider for anchoring
            anchorToControl = control.controls[DIVIDER_PIPPED]
        elseif control.controls[DIVIDER_SIMPLE] then
            anchorToControl = control.controls[DIVIDER_SIMPLE]
        end

        control.headerFocusControl:ClearAnchors()
        if anchorToControl then
            control.headerFocusControl:SetAnchor(TOPLEFT, anchorToControl, BOTTOMLEFT, 0, 0)
        end
    end
end

local function AdjustMessageAnchors(control, refreshResults)
    if refreshResults[MESSAGE] then
        -- Test each anchor target set to see if any one of the controls in that set is in use.  If so, then increase the running offset by a set amount.
        -- The idea here is to anchor the message control to the last label/data row that is in use.
        local additionalOffsetY = 0
        local anchorTargetSets = MESSAGE_ANCHOR_TARGETS[control.layout]
        for _, anchorTargets in ipairs(anchorTargetSets) do
            for _, target in ipairs(anchorTargets) do
                if refreshResults[target] ~= false then
                    additionalOffsetY = additionalOffsetY + ROW_OFFSET_Y
                    break
                end
            end
        end

        -- If there is a head control anchor it under the last data element.
        if control.headerFocusControl then
            additionalOffsetY = additionalOffsetY + ROW_OFFSET_Y
        end

        -- Apply the offset calculated above to the message control's anchors.
        local message = control.controls[MESSAGE]
        for i = 0, MAX_ANCHORS - 1 do
            SetAnchorOffsetY(message, i, ZO_GAMEPAD_CONTENT_DIVIDER_INFO_PADDING_Y + additionalOffsetY)
        end
    end
end

local g_refreshResults = {}

local function SetAlignment(control, alignment, defaultAlignment)
    if control == nil then
        return
    end

    if alignment == nil then
        alignment = defaultAlignment
    end

    control:SetHorizontalAlignment(alignment)
end

function ZO_GamepadGenericHeader_RefreshData(control, data)
    local controls = control.controls

    ProcessData(controls[TITLE], data.titleText)
    ProcessData(controls[SUBTITLE], data.subtitleText)
    SetAlignment(controls[TITLE], data.titleTextAlignment, IsScreenHeader(control.controls) and TEXT_ALIGN_CENTER or TEXT_ALIGN_LEFT)

    g_refreshResults[DATA1HEADER]   = ProcessData(controls[DATA1HEADER], data.data1HeaderText)
    g_refreshResults[DATA1]         = ProcessData(controls[DATA1], data.data1Text)
    g_refreshResults[DATA2HEADER]   = ProcessData(controls[DATA2HEADER], data.data2HeaderText)
    g_refreshResults[DATA2]         = ProcessData(controls[DATA2], data.data2Text)
    g_refreshResults[DATA3HEADER]   = ProcessData(controls[DATA3HEADER], data.data3HeaderText)
    g_refreshResults[DATA3]         = ProcessData(controls[DATA3], data.data3Text)
    g_refreshResults[DATA4HEADER]   = ProcessData(controls[DATA4HEADER], data.data4HeaderText)
    g_refreshResults[DATA4]         = ProcessData(controls[DATA4], data.data4Text)
    g_refreshResults[MESSAGE]       = ProcessData(controls[MESSAGE], data.messageText)
    SetAlignment(controls[MESSAGE], data.messageTextAlignment, TEXT_ALIGN_CENTER)

    ReflowLayout(control)
    AdjustHeaderFocusControlAnchors(control, g_refreshResults)
    AdjustMessageAnchors(control, g_refreshResults)
end

function ZO_GamepadGenericHeader_GetNarrationText(control, data)
    local narration = SCREEN_NARRATION_MANAGER:CreateNarratableObject()
    local controls = control.controls

    if control.tabBar then
        local selectedTabData = control.tabBar:GetSelectedData()
        if selectedTabData then
            local text = selectedTabData.text or ""
            if type(text) == "function" then
                text = text(selectedTabData)
            end
            narration:AddNarrationText(text)
        end
    end

    narration:AddNarrationText(GetProcessedNarrationText(controls[TITLE], data.titleTextNarration or data.titleText))
    narration:AddNarrationText(GetProcessedNarrationText(controls[SUBTITLE], data.subtitleTextNarration or data.subtitleText))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA1HEADER], data.data1HeaderTextNarration or data.data1HeaderText))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA1], data.data1TextNarration or data.data1Text))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA2HEADER], data.data2HeaderTextNarration or data.data2HeaderText))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA2], data.data2TextNarration or data.data2Text))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA3HEADER], data.data3HeaderTextNarration or data.data3HeaderText))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA3], data.data3TextNarration or data.data3Text))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA4HEADER], data.data4HeaderTextNarration or data.data4HeaderText))
    narration:AddNarrationText(GetProcessedNarrationText(controls[DATA4], data.data4TextNarration or data.data4Text))
    narration:AddNarrationText(GetProcessedNarrationText(controls[MESSAGE], data.messageTextNarration or data.messageText))

    return narration
end

local function TabBar_OnDataChanged(control, newData, oldData, reselectingDuringRebuild)
    -- Deactivate header ancestor of tabBar control that is passed in
    -- ei. apply to header at ZO_GamepadInventoryTopLevel from ZO_GamepadInventoryTopLevelMaskContainerHeaderContainerHeaderTabBar
    local parentControl = control.control
    repeat
        local ownerObject = parentControl.owner
        if ownerObject then
            if ownerObject:IsHeaderActive() then
                ownerObject:ExitHeader()
            end
            break
        end
        parentControl = parentControl:GetParent()
    until not parentControl:GetParent()

    if newData.callback then
        newData.callback()
    end
end

function ZO_GamepadGenericHeader_Refresh(control, data, blockTabBarCallbacks)
    ZO_GamepadGenericHeader_RefreshData(control, data)

    if control.tabBar then
        if blockTabBarCallbacks then
            control.tabBar:RemoveOnSelectedDataChangedCallback(TabBar_OnDataChanged)
        else
            control.tabBar:SetOnSelectedDataChangedCallback(TabBar_OnDataChanged)
        end

        if data.activatedCallback then
            control.tabBar:SetOnActivatedChangedFunction(data.activatedCallback)
        end

        local pipsEnabled = false
        local tabBarControl = control.controls[TABBAR]
        local dividerSimpleControl = control.controls[DIVIDER_SIMPLE]
        local dividerPippedControl = control.controls[DIVIDER_PIPPED]
        local pipsControl = nil
        local numEntries = 0

        control.tabBar:Clear()
        if data.tabBarEntries then
            for i, tabData in ipairs(data.tabBarEntries) do
                local visible = tabData.visible
                if type(visible) == "function" then
                    visible = visible()
                end

                if visible ~= false then
                    if tabData.template then
                        control.tabBar:AddEntry(tabData.template, tabData)
                    else
                        control.tabBar:AddEntry("ZO_GamepadTabBarTemplate", tabData)
                    end
                    numEntries = numEntries + 1
                end
            end
            control.tabBar:Commit()

            pipsEnabled = numEntries > 1
            pipsControl = dividerPippedControl:GetNamedChild("Pips")
        end

        tabBarControl:SetHidden(numEntries == 0)
        dividerPippedControl:SetHidden(not pipsEnabled)
        dividerSimpleControl:SetHidden(pipsEnabled)
        control.tabBar:SetPipsEnabled(pipsEnabled, pipsControl)

        if blockTabBarCallbacks then
            control.tabBar:SetOnSelectedDataChangedCallback(TabBar_OnDataChanged)
        end
    end
end

function ZO_GamepadGenericHeader_Activate(control)
    if control.tabBar then
        control.tabBar:Activate()
    end
end

function ZO_GamepadGenericHeader_Deactivate(control)
    if control.tabBar then
        control.tabBar:Deactivate()
    end
end

function ZO_GamepadGenericHeader_SetActiveTabIndex(control, tabIndex, allowEvenIfDisabled)
    if control.tabBar then
        control.tabBar:SetSelectedIndex(tabIndex, allowEvenIfDisabled)
    end
end

function ZO_GamepadGenericHeader_SetTabBarPlaySoundFunction(control, fn)
    if control.tabBar then
        control.tabBar:SetPlaySoundFunction(fn)
    end
end

function ZO_GamepadGenericHeader_SetPipsEnabled(control, enabled)
    if control.tabBar then
        control.tabBar:SetPipsEnabled(enabled)
    end
end