ZO_SortHeaderGroup = ZO_CallbackObject:Subclass()
ZO_SortHeaderGroup.HEADER_CLICKED = "HeaderClicked"
ZO_SortHeaderGroup.SUPPRESS_CALLBACKS = true
ZO_SortHeaderGroup.FORCE_RESELECT = true

local SORT_ARROW_OFFSET_X = 2

function ZO_SortHeaderGroup:New(headerContainer, showArrows)
    local group = ZO_CallbackObject.New(self)

    group.headerContainer = headerContainer
    group.selectedSortHeader= nil
    group.sortDirection = ZO_SORT_ORDER_DOWN
    group.sortHeaders = {}
    group.enabled = true

    group.showArrows = showArrows or false
    if(group.showArrows) then
        group.arrowTexture = CreateControlFromVirtual(headerContainer:GetName().."Arrow", headerContainer, "ZO_SortHeaderArrow")
    end

    group.movementController = ZO_MovementController:New(MOVEMENT_CONTROLLER_DIRECTION_HORIZONTAL)

    -- Default Colors
    group:SetColors(ZO_SELECTED_TEXT, ZO_NORMAL_TEXT, ZO_HIGHLIGHT_TEXT, ZO_DISABLED_TEXT)

    return group
end

function ZO_SortHeaderGroup:AddHeader(header)
    -- Header containers may have other elements like backgrounds/textures.  
    -- Only add controls that have a sort key on them.
    if header.key then
        header.sortHeaderGroup = self
        table.insert(self.sortHeaders, header)
    end
end

function ZO_SortHeaderGroup:AddHeadersFromContainer()
    local headerContainer = self.headerContainer
    for i = 1, headerContainer:GetNumChildren() do
        self:AddHeader(headerContainer:GetChild(i))
    end
end

function ZO_SortHeaderGroup:SetColors(selected, normal, highlight, disabled)
    self.selectedColor = selected
    self.normalColor = normal
    self.highlightColor = highlight
    self.disabledColor = disabled
end

local sortArrowAlignments =
{
    [TEXT_ALIGN_LEFT] = function(arrow, header, textWidth) 
                            arrow:SetAnchor(LEFT, header, LEFT, textWidth + SORT_ARROW_OFFSET_X, 0)
                        end,
    [TEXT_ALIGN_RIGHT] =    function(arrow, header, textWidth) 
                                arrow:SetAnchor(LEFT, header, RIGHT, SORT_ARROW_OFFSET_X, 0)
                            end,
    [TEXT_ALIGN_CENTER] =   function(arrow, header, textWidth) 
                                arrow:SetAnchor(LEFT, header, CENTER, textWidth * 0.5 + SORT_ARROW_OFFSET_X, 0)
                            end,
}

local function UpdateArrowTexture(arrow, sortOrder)
    arrow:SetHidden(false)

    if sortOrder == ZO_SORT_ORDER_UP then
        arrow:SetTexture(ZO_ICON_SORT_ARROW_UP)
    else
        arrow:SetTexture(ZO_ICON_SORT_ARROW_DOWN)
    end
end

local function UpdateTextOrderingTextures(header, arrow, sortOrder)
    UpdateArrowTexture(arrow, sortOrder)

    local nameControl = header:GetNamedChild("Name")
    local textWidth = nameControl:GetTextDimensions()
    local alignmentFn = sortArrowAlignments[nameControl:GetHorizontalAlignment()]
    if alignmentFn then
        arrow:ClearAnchors()
        alignmentFn(arrow, nameControl, textWidth)
    end
end

local function UpdateIconOrderingTextures(header, arrow, sortOrder)
    arrow:SetHidden(true)

    if sortOrder == ZO_SORT_ORDER_UP then
        header:GetNamedChild("Icon"):SetTexture(header.sortUpIcon)
    else
        header:GetNamedChild("Icon"):SetTexture(header.sortDownIcon)
    end
end

local function UpdateIconWithArrowOrderingTextures(header, arrow, sortOrder)
    UpdateArrowTexture(arrow, sortOrder)
    arrow:ClearAnchors()
    arrow:SetAnchor(LEFT, header:GetNamedChild("Icon"), RIGHT, header.arrowOffset)
end

local function ResetIconOrderingTexture(header)
    header:GetNamedChild("Icon"):SetTexture(header.icon)
end

function ZO_SortHeaderGroup:SelectHeader(header)
    header.selected = true
    self.selectedSortHeader = header

    if header.isIconHeader then
        if self.showArrows then
            if header.usesArrow then
                UpdateIconWithArrowOrderingTextures(header, self.arrowTexture, self.sortDirection)
            else
                UpdateIconOrderingTextures(header, self.arrowTexture, self.sortDirection)
            end
        end
    else
        if self.showArrows then
            UpdateTextOrderingTextures(header, self.arrowTexture, self.sortDirection)
        end
        header:GetNamedChild("Name"):SetColor(self.selectedColor:UnpackRGBA())
    end
end

function ZO_SortHeaderGroup:DeselectHeader()
    local oldSelectedHeader = self.selectedSortHeader
    if oldSelectedHeader then
        if oldSelectedHeader.isIconHeader then
            if not oldSelectedHeader.usesArrow then
                ResetIconOrderingTexture(oldSelectedHeader)
            end
        else
            oldSelectedHeader:GetNamedChild("Name"):SetColor(self.normalColor:UnpackRGBA())
        end

        oldSelectedHeader.selected = nil
        self.selectedSortHeader = nil
    end
end

function ZO_SortHeaderGroup:IsCurrentSelectedHeader(header)
    return self.selectedSortHeader == header
end

function ZO_SortHeaderGroup:IsKeyCurrentSelectedIndex(key)
    return self:HeaderForIndex(self.selectedIndex) == self:HeaderForKey(key)
end

function ZO_SortHeaderGroup:GetKeyForCurrentSelectedIndex()
    local header = self:HeaderForIndex(self.selectedIndex)
    if header then
        return header.key
    end
end

function ZO_SortHeaderGroup:OnHeaderClicked(header, suppressCallbacks, forceReselect, forceSortDirection)
    if self:IsEnabled() then
        local resetSortDir = false
        if forceReselect or not self:IsCurrentSelectedHeader(header) then
            self:DeselectHeader()
            resetSortDir = true
        end

        if forceSortDirection ~= nil then
            self.sortDirection = forceSortDirection
        elseif resetSortDir then
            self.sortDirection = header.initialDirection
        else
            self.sortDirection = not self.sortDirection
        end

        self:SelectHeader(header)

        if not suppressCallbacks then
            self:FireCallbacks(self.HEADER_CLICKED, header.key, self.sortDirection)
        end
    end
end

function ZO_SortHeaderGroup:HeaderForKey(key)
    for _, header in ipairs(self.sortHeaders) do
        if header.key == key then
            return header
        end
    end
end

function ZO_SortHeaderGroup:HeaderForIndex(index)
    return self.sortHeaders[index]
end

function ZO_SortHeaderGroup:SelectHeaderByKey(key, suppressCallbacks, forceReselect, forceSortDirection)
    local header = self:HeaderForKey(key)
    if header then
        self:OnHeaderClicked(header, suppressCallbacks, forceReselect, forceSortDirection)
    end
end

function ZO_SortHeaderGroup:SetHeaderHiddenForKey(key, hidden)
    local header = self:HeaderForKey(key)
    if header then
        header:SetHidden(hidden)
    end
end

function ZO_SortHeaderGroup:ReplaceKey(curKey, newKey, newText, selectNewKey)
    local header = self:HeaderForKey(curKey)

    if header then
        header.key = newKey
        if newText then
            self:SetHeaderNameForKey(newKey, newText)
        end

        if selectNewKey or self.selectedSortHeader == header then
            self:SelectAndResetSortForKey(newKey)
            self:MakeSelectedSortHeaderSelectedIndex()
        end
    end
end

-- selects the sort header for the key passed in and will use the initial sort direction for that key instead
-- of toggling the current sort direction
function ZO_SortHeaderGroup:SelectAndResetSortForKey(key)
    local header = self:HeaderForKey(key)
    if header then
        local DONT_SUPPRESS_CALLBACKS = false
        local FORCE_RESELECT = true
        self:SelectHeaderByKey(key, DONT_SUPPRESS_CALLBACKS, FORCE_RESELECT)
    end
end

-- Sets the headers in keyList to whatever the value of "hidden" is, sets the rest to "not hidden"
-- keyList is indexed by key.
function ZO_SortHeaderGroup:SetHeadersHiddenFromKeyList(keyList, hidden)
    for _, header in ipairs(self.sortHeaders) do
        local shouldHide = keyList[header.key]
        if shouldHide and type(shouldHide) == "function" then
            shouldHide = shouldHide()
        end
        local hideHeader = shouldHide and hidden
        header:SetHidden(hideHeader)
    end
end

function ZO_SortHeaderGroup:SetHeaderNameForKey(key, name)
    local header = self:HeaderForKey(key)
    if header then
        header.name = name
        header:GetNamedChild("Name"):SetText(name)
    end
end

function ZO_SortHeaderGroup:GetHeaderNameForKey(key)
    local name = ""
    local header = self:HeaderForKey(key)
    if header then
        name = header.name
    end
    return name
end

function ZO_SortHeaderGroup:GetHeaderNarrationForKey(key)
    local nameNarration = ""
    local header = self:HeaderForKey(key)
    if header then
        --If narrationText is set, use that, otherwise just use the header name
        nameNarration = header.narrationText or header.name
    end
    return nameNarration
end

function ZO_SortHeaderGroup:GetSortHeaderNarrationText(key)
    local narrationText = ""
    local header = self:HeaderForKey(key)
    if header then
        local nameNarration = self:GetHeaderNarrationForKey(key)
        --If this header is the currently active sort, then include the direction
        if header == self.selectedSortHeader then
            local order = self.sortDirection == ZO_SORT_ORDER_DOWN and GetString(SI_SCREEN_NARRATION_SORT_ORDER_DOWN) or GetString(SI_SCREEN_NARRATION_SORT_ORDER_UP)
            narrationText = zo_strformat(SI_SCREEN_NARRATION_SORT_HEADER, nameNarration, order)
        else
            narrationText = nameNarration
        end
    end
    return SCREEN_NARRATION_MANAGER:CreateNarratableObject(narrationText)
end

function ZO_SortHeader_Initialize(control, name, key, initialDirection, alignment, font, highlightTemplate, narrationText)
    local nameControl = control:GetNamedChild("Name")

    if font then
        nameControl:SetFont(font)
    end

    nameControl:SetText(name)
    nameControl:SetHorizontalAlignment(alignment or TEXT_ALIGN_CENTER)
    control.key = key
    control.initialDirection = initialDirection or ZO_SORT_ORDER_DOWN
    control.usesArrow = true
    control.highlightTemplate = highlightTemplate
    control.name = name
    control.narrationText = narrationText
end

function ZO_SortHeader_InitializeIconHeader(control, icon, sortUpIcon, sortDownIcon, mouseoverIcon, key, initialDirection)
    control:GetNamedChild("Icon"):SetTexture(icon)
    local mouseOver = control:GetNamedChild("Mouseover")
    mouseOver:SetTexture(mouseoverIcon)
    mouseOver:SetHidden(true)

    control.icon = icon
    control.sortUpIcon = sortUpIcon
    control.sortDownIcon = sortDownIcon

    control.key = key
    control.initialDirection = initialDirection or ZO_SORT_ORDER_DOWN
    control.isIconHeader = true
end

function ZO_SortHeader_InitializeIconWithArrowHeader(control, icon, mouseoverIcon, arrowOffset, key, initialDirection)
    ZO_SortHeader_InitializeIconHeader(control, icon, nil, nil, mouseoverIcon, key, initialDirection)
    control.usesArrow = true
    control.arrowOffset = arrowOffset
end

function ZO_SortHeader_InitializeArrowHeader(control, key, initialDirection)
    ZO_SortHeader_InitializeIconHeader(control, "EsoUI/Art/Miscellaneous/list_sortHeader_icon_neutral.dds", 
                                                "EsoUI/Art/Miscellaneous/list_sortHeader_icon_sortUp.dds", 
                                                "EsoUI/Art/Miscellaneous/list_sortHeader_icon_sortDown.dds", 
                                                "EsoUI/Art/Miscellaneous/list_sortHeader_icon_over.dds", key, initialDirection)
end

function ZO_SortHeader_SetTooltip(control, tooltipText, point, offsetX, offsetY)
    control.tooltipText = tooltipText
    control.tooltipPoint = point or BOTTOM
    control.tooltipOffsetX = offsetX or 0
    control.tooltipOffsetY = offsetY or -5
end

local function UpdateMouseoverState(group, control, isGroupEnabled)
    local mouseIsOver = control.mouseIsOver

    if control.isIconHeader then
        control:GetNamedChild("Mouseover"):SetHidden(not mouseIsOver)
    else
        local color = group.disabledColor

        if isGroupEnabled then
            if mouseIsOver then
                color = group.highlightColor
            else
                color = control.selected and group.highlightColor or group.normalColor
            end
        end

        control:GetNamedChild("Name"):SetColor(color:UnpackRGBA())
    end

    if control.tooltipText then
        if mouseIsOver then
            InitializeTooltip(InformationTooltip, control, control.tooltipPoint, control.tooltipOffsetX, control.tooltipOffsetY)
            SetTooltipText(InformationTooltip, control.tooltipText)
        else
            ClearTooltip(InformationTooltip)
        end
    end
end

function ZO_SortHeader_OnMouseEnter(control)
    control.mouseIsOver = true
    UpdateMouseoverState(control.sortHeaderGroup, control, control.sortHeaderGroup:IsEnabled())
end

function ZO_SortHeader_OnMouseExit(control)
    control.mouseIsOver = false
    UpdateMouseoverState(control.sortHeaderGroup, control, control.sortHeaderGroup:IsEnabled())
end

function ZO_SortHeader_OnMouseUp(control, upInside)
    if upInside and control.sortHeaderGroup then
        control.sortHeaderGroup:OnHeaderClicked(control)
    end
end

function ZO_SortHeaderGroup:SetEnabled(enabled)
    self.enabled = enabled

    for _, control in ipairs(self.sortHeaders) do
        UpdateMouseoverState(self, control, enabled)
    end
end

function ZO_SortHeaderGroup:IsEnabled()
    return self.enabled
end

local function PlayAnimationOnControl(control, controlTemplate, animationFieldName)
    if controlTemplate then
        if not control[animationFieldName] then
            local highlight = CreateControlFromVirtual("$(parent)Scroll", control, controlTemplate, animationFieldName)
            control[animationFieldName] = ANIMATION_MANAGER:CreateTimelineFromVirtual("ShowOnMouseOverLabelAnimation", highlight)
        end
        control[animationFieldName]:PlayForward()
    end
end

local function RemoveAnimationOnControl(control, animationFieldName)
    if control[animationFieldName] then
        control[animationFieldName]:PlayBackward()
    end
end

local function HighlightControl(self, control)
    local highlightTemplate = control.highlightTemplate or self.highlightTemplate
    PlayAnimationOnControl(control, highlightTemplate, "HighlightAnimation")

    if self.highlightCallback then
        self.highlightCallback(control, true)
    end
end

local function UnhighlightControl(self, control) 
    RemoveAnimationOnControl(control, "HighlightAnimation")

    if self.highlightCallback then
        self.highlightCallback(control, false)
    end
end

function ZO_SortHeaderGroup:EnableSelection(enabled)
    self.selectionEnabled = enabled
    if enabled then
        self:SetSelectedIndex(self.selectedIndex or 1)
    else
        self:SetSelectedIndex(nil)
    end
    
end

function ZO_SortHeaderGroup:EnableHighlight(highlightTemplate, highlightCallback)
    if not self.highlightTemplate then
        self.highlightTemplate = highlightTemplate
        self.highlightCallback = highlightCallback
    end
end

function ZO_SortHeaderGroup:SetDirectionalInputEnabled(enabled)
    self.directionalInputEnabled = enabled
    if enabled then
        DIRECTIONAL_INPUT:Activate(self, self.headerContainer)
    else
        DIRECTIONAL_INPUT:Deactivate(self)
    end
end

function ZO_SortHeaderGroup:UpdateDirectionalInput()
    -- No need to check the directional input (and consume it so no one else can use it)
    -- if we don't have multiple headers to switch between
    if #self.sortHeaders > 1 then
        local result = self.movementController:CheckMovement()
        if result == MOVEMENT_CONTROLLER_MOVE_NEXT then
            self:MoveNext()
        elseif result == MOVEMENT_CONTROLLER_MOVE_PREVIOUS then
            self:MovePrevious()
        end
    end
end

function ZO_SortHeaderGroup:SetSelectedIndex(selectedIndex)
    if self.selectedIndex then
        local previousSelectedControl = self.sortHeaders[self.selectedIndex]
        UnhighlightControl(self, previousSelectedControl)
    end

    if not selectedIndex then
        return
    end

    if #self.sortHeaders <= 0 then
        return
    end

    selectedIndex = zo_clamp(selectedIndex, 1, #self.sortHeaders)
    local selectedControl = self.sortHeaders[selectedIndex]
    HighlightControl(self, selectedControl)
    self.selectedIndex = selectedIndex
    self:FireCallbacks("HeaderSelected", selectedControl.key)
end

function ZO_SortHeaderGroup:MakeSelectedSortHeaderSelectedIndex()
    if self.selectedSortHeader then
        for index, header in ipairs(self.sortHeaders) do
            if header == self.selectedSortHeader then
                self:SetSelectedIndex(index)
                break
            end
        end
    end
end

do
    local CHECK_FORWARD = 1
    local CHECK_BACKWARD = -1

    --On Gamepad, we need to check for and ignore hidden headers
    function ZO_SortHeaderGroup:FindNextActiveHeaderIndex(checkDirection)
        local checkIndex = self.selectedIndex
        local activeHeaderFound = false
        while not activeHeaderFound do
            checkIndex = checkIndex + checkDirection
            local checkControl = self.sortHeaders[checkIndex]
            if checkControl then
                activeHeaderFound = not checkControl:IsHidden()
            else
                break
            end
        end
        return activeHeaderFound and checkIndex or self.selectedIndex
    end

    function ZO_SortHeaderGroup:MovePrevious()
        local previousActiveIndex = self:FindNextActiveHeaderIndex(CHECK_BACKWARD)
        self:SetSelectedIndex(self.selectedIndex and previousActiveIndex)
    end

    function ZO_SortHeaderGroup:MoveNext()
        local nextActiveIndex = self:FindNextActiveHeaderIndex(CHECK_FORWARD)
        self:SetSelectedIndex(self.selectedIndex and nextActiveIndex)
    end
end

function ZO_SortHeaderGroup:GetSelectedData()
end

function ZO_SortHeaderGroup:GetCurrentSortKey()
    return self.selectedSortHeader and self.selectedSortHeader.key
end

function ZO_SortHeaderGroup:GetSortDirection()
    return self.sortDirection
end

function ZO_SortHeaderGroup:SortBySelected()
    if not self.selectedIndex then return end
    local selectedControl = self.sortHeaders[self.selectedIndex]

    self:OnHeaderClicked(selectedControl)
end