--[[
    Base class for combo box ui widgets.
    Subclasses are responsible for the look of the combo box.
--]]

ZO_ComboBox_Base = ZO_InitializingObject:Subclass()

ZO_COMBOBOX_UPDATE_NOW = 1
ZO_COMBOBOX_SUPPRESS_UPDATE = 2

function ZO_ComboBox_Base:ShowDropdownInternal()
    -- this is meant to be overridden by a subclass it's called when the combo dropdown is to be shown
    -- this function should populate the list using the entries in m_sortedItems and make the dropdown visible
end

function ZO_ComboBox_Base:HideDropdownInternal()
    -- this is meant to be overridden by a subclass 
    -- this function should clear the list and hide the dropdown
end

function ZO_ComboBox_Base:OnClearItems()
    -- this can optionally be overriden by a subclass and is called when the contents of a combo box are cleared
end

function ZO_ComboBox_Base:OnItemAdded()
    -- this can optionally be overriden by a subclass and is called when a new entry is added to the combo box
end

function ZO_ComboBox_Base:Initialize(control)
    self.m_container = control
    self.m_selectedItemText = control:GetNamedChild("SelectedItemText")
    self.m_selectedItemData = nil
    self.m_openDropdown = control:GetNamedChild("OpenDropdown")
    self.m_selectedColor = { GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED) }
    self.m_disabledColor = ZO_ERROR_COLOR
    self.m_sortOrder = ZO_SORT_ORDER_UP
    self.m_sortType = ZO_SORT_BY_NAME
    self.m_sortsItems = true
    self.m_sortedItems = {}
    self.m_isDropdownVisible = false
    self.m_font = nil
    self.m_preshowDropdownFn = nil
    self.m_spacing = 0
    self.m_name = control:GetName()
    self.horizontalAlignment = TEXT_ALIGN_LEFT
    control.m_comboBox = self
end

function ZO_ComboBox_Base:GetContainer()
    return self.m_container
end

function ZO_ComboBox_Base:SetPreshowDropdownCallback(fn)
    -- Called right before the menu is shown.
    self.m_preshowDropdownFn = fn
end

function ZO_ComboBox_Base:SetFont(font)
    self:SetSelectedItemFont(font)
    self:SetDropdownFont(font)
end

function ZO_ComboBox_Base:SetDropdownFont(font)
    self.m_font = font
end

function ZO_ComboBox_Base:GetDropdownFont()
    return self.m_font
end

function ZO_ComboBox_Base:GetDropdownFontObject()
    return _G[self.m_font]
end

function ZO_ComboBox_Base:SetSelectedItemFont(font)
    self.m_selectedItemText:SetFont(font)
end

function ZO_ComboBox_Base:SetSpacing(spacing)
    self.m_spacing = spacing
end

function ZO_ComboBox_Base:GetSpacing()
    return self.m_spacing
end

function ZO_ComboBox_Base:SetSelectedColor(color, colorG, colorB, colorA)
    if type(color) == "table" then
        color, colorG, colorB, colorA = color:UnpackRGBA()
    end

    self.m_selectedColor = { color, colorG, colorB, colorA }
    self.m_selectedItemText:SetColor(color, colorG, colorB, colorA)
end

function ZO_ComboBox_Base:SetDisabledColor(color, colorG, colorB, colorA)
    self.m_disabledColor = ZO_ColorDef:New(color, colorG, colorB, colorA)
end

function ZO_ComboBox_Base:SetNormalColor(color, colorG, colorB, colorA)
    self.m_normalColor = ZO_ColorDef:New(color, colorG, colorB, colorA)
end

function ZO_ComboBox_Base:SetHighlightedColor(color, colorG, colorB, colorA)
    self.m_highlightColor = ZO_ColorDef:New(color, colorG, colorB, colorA)
end

function ZO_ComboBox_Base:SetSelectedItemTextColor(selected)
    if selected then
        self.m_selectedItemText:SetColor(self.m_highlightColor:UnpackRGB())
    else
        self.m_selectedItemText:SetColor(self.m_normalColor:UnpackRGB())
    end
end

function ZO_ComboBox_Base:SetSortsItems(sortsItems)
    self.m_sortsItems = sortsItems
end

function ZO_ComboBox_Base:SetSortOrder(sortOrder, sortType)
    self.m_sortOrder = sortOrder or ZO_SORT_ORDER_UP
    self.m_sortType = sortType or ZO_SORT_BY_NAME
    self:UpdateItems()
end

function ZO_ComboBox_Base:IsDropdownVisible()
    return self.m_isDropdownVisible
end

function ZO_ComboBox_Base:SetVisible(visible)
    self.m_isDropdownVisible = visible
end

function ZO_ComboBox_Base:CreateItemEntry(name, callback, enabled)
    local isEnabled = enabled
    if isEnabled == nil then
        -- Evaluate nil to be equivalent to true for backwards compatibility.
        isEnabled = true
    end

    local itemEntry =
    {
        name = name,
        callback = callback,
        enabled = isEnabled,
    }
    return itemEntry
end

function ZO_ComboBox_Base:AddItem(itemEntry, updateOptions)
    table.insert(self.m_sortedItems, itemEntry)
    
    if updateOptions ~= ZO_COMBOBOX_SUPPRESS_UPDATE then
        self:UpdateItems()
    end

    self:OnItemAdded()
end

function ZO_ComboBox_Base:ShowDropdown()
    -- Let the caller know that this is about to be shown...
    if self.m_preshowDropdownFn then
        self.m_preshowDropdownFn(self)
    end
    self:ShowDropdownInternal()
end

function ZO_ComboBox_Base:HideDropdown()
    if self:IsDropdownVisible() then
        self:HideDropdownInternal()
    end
end

function ZO_ComboBox_Base:ClearItems()
    ZO_ComboBox_HideDropdown(self:GetContainer())
    ZO_ClearNumericallyIndexedTable(self.m_sortedItems)
    self:SetSelectedItemText("")
    self.m_selectedItemData = nil
    self:OnClearItems()
end

function ZO_ComboBox_Base:GetItems()
    return self.m_sortedItems
end

function ZO_ComboBox_Base:GetNumItems()
    return #self.m_sortedItems
end

function ZO_ComboBox_Base:AddItems(items)
    for k, v in pairs(items) do
        self:AddItem(v, ZO_COMBOBOX_SUPPRESS_UPDATE)
    end
    
    self:UpdateItems()
end

local function ComboBoxSortHelper(item1, item2, comboBoxObject)
    return ZO_TableOrderingFunction(item1, item2, "name", comboBoxObject.m_sortType, comboBoxObject.m_sortOrder)
end

function ZO_ComboBox_Base:UpdateItems()
    if self.m_sortOrder and self.m_sortsItems then
        table.sort(self.m_sortedItems, function(item1, item2) return ComboBoxSortHelper(item1, item2, self) end)
    end
    
    if self:IsDropdownVisible() then
        self:ShowDropdown()
    end
end

--This is used if selection of an entry should go through some other avenue to determine the correct display text (e.g.: Chat System)
function ZO_ComboBox_Base:SetDontSetSelectedTextOnSelection(dontSetSelectedTextOnSelection)
    self.dontSetSelectedTextOnSelection = dontSetSelectedTextOnSelection
end

function ZO_ComboBox_Base:SetSelectedItemText(itemText)
    if self.m_selectedItemText then
        self.m_selectedItemText:SetText(itemText)
        self.currentSelectedItemText = itemText
    end
end

--Maintain this for addons, but use the better named SetSelectedItemText
function ZO_ComboBox_Base:SetSelectedItem(itemText)
    self:SetSelectedItemText(itemText)
end

function ZO_ComboBox_Base:ItemSelectedClickHelper(item, ignoreCallback)
    if item.enabled == false then
        return false
    end

    local oldItem = self.m_selectedItemData
    if self.dontSetSelectedTextOnSelection ~= true then
        self:SetSelectedItemText(item.name)
    end
    self.m_selectedItemData = item

    if not ignoreCallback then
        PlaySound(SOUNDS.COMBO_CLICK)

        if item.callback then
            local selectionChanged = (oldItem ~= item)
            if not selectionChanged and oldItem and item then
                selectionChanged = item.name ~= oldItem.name
            end
            item.callback(self, item.name, item, selectionChanged, oldItem)
        end
    end

    return true
end

--Maintain for addons
function ZO_ComboBox_Base_ItemSelectedClickHelper(comboBox, item, ignoreCallback)
    return comboBox:ItemSelectedClickHelper(item, ignoreCallback)
end

function ZO_ComboBox_Base:SelectItem(item, ignoreCallback)
    if item then
        return self:ItemSelectedClickHelper(item, ignoreCallback)
    end
end

function ZO_ComboBox_Base:SelectItemByIndex(index, ignoreCallback)
    return self:SelectItem(self.m_sortedItems[index], ignoreCallback)
end

function ZO_ComboBox_Base:SelectFirstItem(ignoreCallback)
    return self:SelectItemByIndex(1, ignoreCallback)
end

function ZO_ComboBox_Base:GetIndexByEval(eval)
    for i, item in ipairs(self.m_sortedItems) do
        if eval(item) then
            return i
        end
    end
    return nil
end

function ZO_ComboBox_Base:SetSelectedItemByEval(eval, ignoreCallback)
    local index = self:GetIndexByEval(eval)
    if index then
        return self:SelectItemByIndex(index, ignoreCallback)
    end
    return false
end

function ZO_ComboBox_Base:GetSelectedItem()
    return self.m_selectedItemText:GetText()
end

function ZO_ComboBox_Base:GetSelectedItemData()
    return self.m_selectedItemData
end

function ZO_ComboBox_Base:EnumerateEntries(functor)
    for index, data in ipairs(self.m_sortedItems) do
        if functor(index, data) then
            break -- once the enumeration is complete, returning true means stop enumerating
        end
    end
end

function ZO_ComboBox_Base:GetSelectedTextColor(enabledState)
    if enabledState then
        return unpack(self.m_selectedColor)
    else
        return GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_DISABLED)
    end
end

function ZO_ComboBox_Base:GetItemNormalColor(item)
    if item.enabled == false then
        return item.m_disabledColor or self.m_disabledColor
    end
    return item.m_normalColor or self.m_normalColor
end

function ZO_ComboBox_Base:GetItemHighlightColor(item)
    if item.enabled == false then
        return item.m_disabledColor or self.m_disabledColor
    end
    return item.m_highlightColor or self.m_highlightColor
end

function ZO_ComboBox_Base:SetItemEnabled(item, enabled)
    local isEnabled = enabled
    if isEnabled == nil then
        -- Evaluate nil to be equivalent to true for backwards compatibility.
        isEnabled = true
    end
    item.enabled = isEnabled
end

function ZO_ComboBox_Base:SetItemOnEnter(item, handler)
    item.onEnter = handler
end

function ZO_ComboBox_Base:SetItemOnExit(item, handler)
    item.onExit = handler
end

function ZO_ComboBox_Base:GetControl()
    return self.m_container
end

function ZO_ComboBox_Base:SetHorizontalAlignment(alignment)
    self.horizontalAlignment = alignment
    self.m_selectedItemText:SetHorizontalAlignment(alignment)
end

--[[
    Utilities to obtain the combo box from the container.
--]]

function ZO_ComboBox_ObjectFromContainer(container)
    return container.m_comboBox
end

function ZO_ComboBox_OpenDropdown(container)
    local comboBox = ZO_ComboBox_ObjectFromContainer(container)

    if not comboBox:IsDropdownVisible() then
        comboBox:ShowDropdown()
    end
end

function ZO_ComboBox_HideDropdown(container)
    local comboBox = ZO_ComboBox_ObjectFromContainer(container)

    if comboBox then
        comboBox:HideDropdown()
    end
end

function ZO_ComboBox_Enable(container)
    ZO_ComboBox_ObjectFromContainer(container):SetEnabled(true)
end

function ZO_ComboBox_Disable(container)
    ZO_ComboBox_ObjectFromContainer(container):SetEnabled(false)
end