---@class StdUi
local StdUi = LibStub and LibStub('StdUi', true)
if not StdUi then
	return
end

local module, version = 'Autocomplete', 4
if not StdUi:UpgradeNeeded(module, version) then
	return
end

local TableInsert = tinsert

StdUi.Util.autocompleteTransformer = function(_, value)
	return value
end

StdUi.Util.autocompleteValidator = function(self)
	self.stdUi:MarkAsValid(self, true)
	return true
end

StdUi.Util.autocompleteItemTransformer = function(_, value)
	if not value or value == '' then
		return value
	end

	local itemName = GetItemInfo(value)
	return itemName
end

StdUi.Util.autocompleteItemValidator = function(self, ac)
	local itemName, itemId
	local t = ac:GetText()
	local v = ac:GetValue()

	if tonumber(t) ~= nil then
		-- it's a number
		itemName = GetItemInfo(tonumber(t))
		if itemName then
			itemId = tonumber(t)
		end
	elseif v then
		itemName = GetItemInfo(v)
		if itemName == t then
			itemId = v
		end
	end

	if itemId then
		ac.value = itemId
		ac:SetText(itemName)
		self.stdUi:MarkAsValid(ac, true)

		return true
	else
		self.stdUi:MarkAsValid(ac, false)
		return false
	end
end

local AutocompleteMethods = {
	--- Private methods
	buttonCreate = function(panel)
		local optionButton

		optionButton = StdUi:HighlightButton(panel, panel:GetWidth(), 20, '')
		optionButton.highlight = StdUi:HighlightButtonTexture(optionButton)
		optionButton.highlight:Hide()
		optionButton.text:SetJustifyH('LEFT')
		optionButton.autocomplete = panel.autocomplete
		optionButton:SetFrameLevel(panel:GetFrameLevel() + 2)

		optionButton:SetScript(
			'OnClick',
			function(b)
				local ac = b.autocomplete
				if b.boundItem then
					b.autocomplete.selectedItem = b.boundItem
				end

				ac.indexChosen = 0
				ac:SetValue(b.value, b:GetText())
				b.autocomplete.dropdown:Hide()
				ac:SetFocus()
			end
		)

		return optionButton
	end,
	buttonUpdate = function(panel, optionButton, data)
		optionButton.boundItem = data
		optionButton.value = data.value

		optionButton:SetWidth(panel:GetWidth())
		optionButton:SetText(data.text)
	end,
	filterItems = function(ac, search, itemsToSearch)
		local result = {}

		for _, item in pairs(itemsToSearch) do
			local valueString = tostring(item.value)
			if item.text:lower():find(search:lower(), nil, true) or valueString:lower():find(search:lower(), nil, true) then
				TableInsert(result, item)
			end

			if #result >= ac.itemLimit then
				break
			end
		end

		return result
	end,
	--- Public methods
	SetItems = function(self, newItems)
		self.items = newItems
		self:RenderItems()
		self.dropdown:Hide()
	end,
	RenderItems = function(self)
		local dropdownHeight = 20 * #self.filteredItems
		self.dropdown:SetHeight(dropdownHeight)

		self.stdUi:ObjectList(self.dropdown, self.itemTable, self.buttonCreate, self.buttonUpdate, self.filteredItems)
	end,
	ToggleHighlightItems = function(self, flag)
		for _, button in pairs(self.itemTable) do
			if flag then
				button.highlight:Show()
			else
				button.highlight:Hide()
			end
		end
	end,
	ToggleHighlight = function(self, index, flag)
		if flag then
			self.itemTable[index].highlight:Show()
		else
			self.itemTable[index].highlight:Hide()
		end
	end,
	ValueToText = function(self, value)
		return self.transformer(value)
	end,
	SetValue = function(self, value, t)
		self.value = value
		self:SetText(t or self:ValueToText(value) or '')
		self:Validate()
		self.button:Hide()
	end,
	Validate = function(self)
		self.isValidated = true
		self.isValid = self:validator()

		if self.isValid then
			if self.OnValueChanged then
				self:OnValueChanged(self.value, self:GetText())
			end
		end
		self.isValidated = false
	end
}

local AutocompleteEvents = {
	OnEditFocusLost = function(s)
		C_Timer.After(
			0.1,
			function()
				s.dropdown:Hide()
			end
		)
	end,
	OnEnterPressed = function(ac)
		ac.dropdown:Hide()

		-- User was using arrows to select item
		if ac.indexChosen > 0 and ac.indexChosen <= #ac.filteredItems then
			local item = ac.filteredItems[ac.indexChosen]
			ac:SetValue(item.value, item.text)
			ac.indexChosen = 0
			ac:ToggleHighlightItems(false)
		end
		ac:Validate()

		if ac.CustomEnterPressed then
			ac:CustomEnterPressed(ac)
		end
	end,
	OnTextChanged = function(ac, isUserInput)
		local plainText = StdUi.Util.stripColors(ac:GetText())
		ac.selectedItem = nil

		if isUserInput then
			-- reset value if user changed something
			ac.value = nil

			if type(ac.items) == 'function' then
				-- We ensure to pass whole autocomplete as well
				ac.filteredItems = ac:items(plainText)
			elseif type(ac.items) == 'table' then
				ac.filteredItems = ac:filterItems(plainText, ac.items)
			end

			if not ac.filteredItems or #ac.filteredItems == 0 then
				ac.dropdown:Hide()
			else
				ac:RenderItems()
				ac.dropdown:Show()
			end
		end
	end,
	OnKeyUp = function(ac, key)
		local arrowKeyPressed = false
		if key == 'UP' then
			ac.indexChosen = ac.indexChosen - 1
			arrowKeyPressed = true
		elseif key == 'DOWN' then
			ac.indexChosen = ac.indexChosen + 1
			arrowKeyPressed = true
		end

		if arrowKeyPressed then
			if ac.indexChosen < 1 then
				ac.indexChosen = #ac.filteredItems
			end

			if ac.indexChosen > #ac.filteredItems then
				ac.indexChosen = 1
			end

			ac:ToggleHighlightItems(false)
			ac:ToggleHighlight(ac.indexChosen, true)
		end
	end
}

--- Very similar to dropdown except it has the ability to create new records and filters results
---@return EditBox
function StdUi:Autocomplete(parent, width, height, text, validator, transformer, items)
	transformer = transformer or StdUi.Util.autocompleteTransformer
	validator = validator or StdUi.Util.autocompleteValidator

	local autocomplete = self:EditBox(parent, width, height, text, validator)
	---@type StdUi
	autocomplete.stdUi = self
	autocomplete.transformer = transformer
	autocomplete.items = items
	autocomplete.filteredItems = {}
	autocomplete.selectedItem = nil
	autocomplete.itemLimit = 8
	autocomplete.itemTable = {}
	autocomplete.indexChosen = 0

	autocomplete.dropdown = self:Panel(parent, width, 20)
	autocomplete.dropdown:SetPoint('TOPLEFT', autocomplete, 'BOTTOMLEFT', 0, 0)
	autocomplete.dropdown:SetPoint('TOPRIGHT', autocomplete, 'BOTTOMRIGHT', 0, 0)
	autocomplete.dropdown:Hide()
	autocomplete.dropdown:SetFrameLevel(autocomplete:GetFrameLevel() + 10)

	-- keep back reference
	autocomplete.dropdown.autocomplete = autocomplete

	for k, v in pairs(AutocompleteMethods) do
		autocomplete[k] = v
	end

	for k, v in pairs(AutocompleteEvents) do
		autocomplete:SetScript(k, v)
	end

	return autocomplete
end

StdUi:RegisterModule(module, version)
