-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                          https://tradeskillmaster.com                          --
--    All Rights Reserved - Detailed license information included with addon.     --
-- ------------------------------------------------------------------------------ --

--- Base Dropdown UI Element Class.
-- The base dropdown class is an abstract class which provides shared functionality between the @{SelectionDropdown} and
-- @{MultiselectionDropdown} classes. It is a subclass of the @{Text} class.
-- @classmod BaseDropdown

local _, TSM = ...
local NineSlice = TSM.Include("Util.NineSlice")
local Color = TSM.Include("Util.Color")
local Theme = TSM.Include("Util.Theme")
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
local BaseDropdown = TSM.Include("LibTSMClass").DefineClass("BaseDropdown", TSM.UI.Text, "ABSTRACT")
local UIElements = TSM.Include("UI.UIElements")
UIElements.Register(BaseDropdown)
TSM.UI.BaseDropdown = BaseDropdown
local private = {}
local EXPANDER_SIZE = 18
local TEXT_PADDING = 8
local EXPANDER_PADDING = 8



-- ============================================================================
-- Meta Class Methods
-- ============================================================================

function BaseDropdown.__init(self)
	local frame = UIElements.CreateFrame(self, "Button", nil, nil, nil)

	self.__super:__init(frame)

	self._nineSlice = NineSlice.New(frame)

	ScriptWrapper.Set(frame, "OnClick", private.FrameOnClick, self)
	frame.arrow = frame:CreateTexture(nil, "ARTWORK")

	self._widthText = UIElements.CreateFontString(self, frame)
	self._widthText:Hide()

	self._font = "BODY_BODY2"
	self._hintText = ""
	self._items = {}
	self._itemKeyLookup = {}
	self._disabled = false
	self._isOpen = false
	self._onSelectionChangedHandler = nil
end

function BaseDropdown.Release(self)
	self._hintText = ""
	wipe(self._items)
	wipe(self._itemKeyLookup)
	self._disabled = false
	self._isOpen = false
	self._onSelectionChangedHandler = nil
	self:_GetBaseFrame():Enable()
	self.__super:Release()
	self._font = "BODY_BODY2"
end



-- ============================================================================
-- Public Class Methods
-- ============================================================================

--- Sets the hint text which is shown when there's no selection.
-- @tparam BaseDropdown self The dropdown object
-- @tparam string text The hint text string
-- @treturn BaseDropdown The dropdown object
function BaseDropdown.SetHintText(self, text)
	self._hintText = text
	return self
end

--- Add an item to be shown in the dropdown dialog list.
-- @tparam BaseDropdown self The dropdown object
-- @tparam string item The item to add to the list (localized string)
-- @tparam[opt] string|number itemKey The internal representation of the item (if not specified will be the index)
-- @treturn BaseDropdown The dropdown object
function BaseDropdown.AddItem(self, item, itemKey)
	tinsert(self._items, item)
	self._itemKeyLookup[item] = itemKey or #self._items
	return self
end

--- Set the items to show in the dropdown dialog list.
-- @tparam BaseDropdown self The dropdown object
-- @tparam table items A list of items to be shown in the dropdown list
-- @tparam[opt] table itemKeys A list of keys which go with the item at the corresponding index in the items list
-- @treturn BaseDropdown The dropdown object
function BaseDropdown.SetItems(self, items, itemKeys)
	wipe(self._items)
	wipe(self._itemKeyLookup)
	assert(not itemKeys or #itemKeys == #items)
	for i, item in ipairs(items) do
		self:AddItem(item, itemKeys and itemKeys[i])
	end
	return self
end

--- Set whether or not the dropdown is disabled.
-- @tparam BaseDropdown self The dropdown object
-- @tparam boolean disabled Whether or not to disable the dropdown
-- @treturn BaseDropdown The dropdown object
function BaseDropdown.SetDisabled(self, disabled)
	self._disabled = disabled
	if disabled then
		self:_GetBaseFrame():Disable()
	else
		self:_GetBaseFrame():Enable()
	end
	return self
end

--- Registers a script handler.
-- @tparam BaseDropdown self The dropdown object
-- @tparam string script The script to register for (supported scripts: `OnSelectionChanged`)
-- @tparam function handler The script handler which will be called with the dropdown object followed by any arguments
-- to the script
-- @treturn BaseDropdown The dropdown object
function BaseDropdown.SetScript(self, script, handler)
	if script == "OnSelectionChanged" then
		self._onSelectionChangedHandler = handler
	else
		error("Invalid BaseDropdown script: "..tostring(script))
	end
	return self
end

--- Sets whether or not the dropdown is open.
-- @tparam BaseDropdown self The dropdown object
-- @tparam boolean open Whether or not the dropdown is open
-- @treturn BaseDropdown The dropdown object
function BaseDropdown.SetOpen(self, open)
	assert(type(open) == "boolean")
	if open == self._isOpen then
		return self
	end
	self._isOpen = open
	if open then
		local width, height = self:_GetDialogSize()
		local dialogFrame = UIElements.New("Frame", "dropdown")
			:SetLayout("VERTICAL")
			:SetContext(self)
			:AddAnchor("TOPLEFT", self:_GetBaseFrame(), "BOTTOMLEFT", 0, -4)
			:SetPadding(0, 0, 4, 4)
			:SetBackgroundColor("ACTIVE_BG", true)
			:SetSize(max(width, self:_GetDimension("WIDTH")), height)
			:SetScript("OnHide", private.DialogOnHide)
		self:_AddDialogChildren(dialogFrame)
		dialogFrame:GetElement("list"):SetScript("OnSelectionChanged", private.ListOnSelectionChanged)
		self:GetBaseElement():ShowDialogFrame(dialogFrame)
	else
		self:GetBaseElement():HideDialog()
	end
	return self
end

function BaseDropdown.SetText(self)
	error("BaseDropdown does not support this method")
end

function BaseDropdown.SetTextColor(self, color)
	error("BaseDropdown does not support this method")
end

function BaseDropdown.Draw(self)
	self.__super:SetText(self:_GetCurrentSelectionString())
	self.__super:Draw()
	local frame = self:_GetBaseFrame()
	TSM.UI.TexturePacks.SetTexture(frame.arrow, "iconPack.18x18/Chevron/Down")
	local frameHeight = frame:GetHeight()
	local paddingX = EXPANDER_PADDING
	local paddingY = (frameHeight - EXPANDER_SIZE) / 2
	frame.text:ClearAllPoints()
	frame.text:SetPoint("TOPLEFT", TEXT_PADDING, 0)
	frame.text:SetPoint("BOTTOMRIGHT", -EXPANDER_SIZE, 0)
	frame.arrow:ClearAllPoints()
	frame.arrow:SetPoint("BOTTOMLEFT", frame.text, "BOTTOMRIGHT", -paddingX, paddingY)
	frame.arrow:SetPoint("TOPRIGHT", -paddingX, -paddingY)

	-- set textures and text color depending on the state
	self._nineSlice:SetStyle("rounded")
	local textColor = self:_GetTextColor()
	frame.text:SetTextColor(textColor:GetFractionalRGBA())
	self._nineSlice:SetVertexColor(Theme.GetColor(self._disabled and "PRIMARY_BG_ALT" or "ACTIVE_BG"):GetFractionalRGBA())
	frame.arrow:SetVertexColor(textColor:GetFractionalRGBA())
end



-- ============================================================================
-- Private Class Methods
-- ============================================================================

function BaseDropdown._GetTextColor(self)
	local color = Theme.GetColor(self._disabled and "PRIMARY_BG_ALT" or "ACTIVE_BG")
	-- the text color should have maximum contrast with the dropdown color, so set it to white/black based on the dropdown color
	if color:IsLight() then
		-- the dropdown is light, so set the text to black
		return Color.GetFullBlack():GetTint(self._disabled and "-DISABLED" or 0)
	else
		-- the dropdown is dark, so set the text to white
		return Color.GetFullWhite():GetTint(self._disabled and "+DISABLED" or 0)
	end
end

function BaseDropdown._GetDialogSize(self)
	local maxStringWidth = 100 -- no smaller than 100
	self._widthText:Show()
	self._widthText:SetFont(Theme.GetFont(self._font):GetWowFont())
	for _, item in ipairs(self._items) do
		self._widthText:SetText(item)
		maxStringWidth = max(maxStringWidth, self._widthText:GetUnboundedStringWidth())
	end
	self._widthText:Hide()
	return maxStringWidth + Theme.GetColSpacing() * 2, 8 + max(16, min(8, #self._items) * 20)
end



-- ============================================================================
-- Local Script Handlers
-- ============================================================================

function private.FrameOnClick(self)
	self:SetOpen(true)
end

function private.ListOnSelectionChanged(dropdownList, selection)
	local self = dropdownList:GetParentElement():GetContext()
	self:_OnListSelectionChanged(dropdownList, selection)
	self:Draw()
end

function private.DialogOnHide(frame)
	local self = frame:GetContext()
	self._isOpen = false
end
