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

local module, version = 'Autocomplete', 5;
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 = C_Item.GetItemInfo(value);
	return itemName;
end

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

	if tonumber(t) ~= nil then
		-- it's a number
		itemName = C_Item.GetItemInfo(tonumber(t));
		if itemName then
			itemId = tonumber(t);
		end
	elseif v then
		itemName = C_Item.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);