--[[
	ItemSearch
		An item text search engine of some sort
--]]

local Search = LibStub("CustomSearch-1.0")
local Unfit = LibStub("Unfit-1.0")
local Lib = LibStub:NewLibrary("LibItemSearch-1.2-ElvUI", 17)
if Lib then
	Lib.Scanner = LibItemSearchTooltipScanner or CreateFrame("GameTooltip", "LibItemSearchTooltipScanner", UIParent, "GameTooltipTemplate")
	Lib.Filters = {}
else
	return
end

--[[ User API ]]--

function Lib:Matches(link, search)
	return Search(link, search, self.Filters)
end

function Lib:Tooltip(link, search)
	return link and self.Filters.tip:match(link, nil, search)
end

function Lib:TooltipPhrase(link, search)
	return link and self.Filters.tipPhrases:match(link, nil, search)
end

function Lib:InSet(link, search)
	if IsEquippableItem(link) then
		local id = tonumber(link:match("item:(%-?%d+)"))
		return self:BelongsToSet(id, (search or ""):lower())
	end
end

--[[ Internal API ]]--
local function LibItemRack(id, search)
	for name, set in pairs(ItemRackUser.Sets) do
		if search == "any" or name:sub(1,1) ~= "" and Search:Find(search, name) then
			for _, item in pairs(set.equip) do
				if sameID(id, item) then
					return true
				end
			end
		end
	end
end

local function LibWardrobe(id, search)
	for _, outfit in ipairs(Wardrobe.CurrentConfig.Outfit) do
		local name = outfit.OutfitName
		if search == "any" or Search:Find(search, name) then
			for _, item in pairs(outfit.Item) do
				if item.IsSlotUsed == 1 and item.ItemID == id then
					return true
				end
			end
		end
	end
end

local function LibOutfitter(id, search)
	for _, group in pairs(Outfitter.Settings.Outfits) do
		for _, set in ipairs(group) do
			if search == "any" or Search:Find(search, set["Name"]) then
				for _, base in pairs(set["Items"]) do
					if base["Code"] == id then
						return true
					end 
				end
			end
		end
	end
end

local function LibDefault(id, search)
	for i = 1, GetNumEquipmentSets() do
		local name = GetEquipmentSetInfo(i)
		if search == "any" or Search:Find(search, name) then
			local items = GetEquipmentSetItemIDs(name)
			for _, item in pairs(items) do
				if id == item then
					return true
				end
			end
		end
	end
end

local outfit_addons = {LibDefault}
function Lib:BelongsToSet(id, search)
	for g, funct in ipairs(outfit_addons) do
		if funct(id, search) then
			return true
		end
	end
end



--[[ General ]]--

Lib.Filters.name = {
	tags = {"n", "name"},

	canSearch = function(self, operator, search)
		return not operator and search
	end,

	match = function(self, item, _, search)
		local name = item:match("%[(.-)%]")
		return Search:Find(search, name)
	end
}

Lib.Filters.type = {
	tags = {"t", "type", "s", "slot"},

	canSearch = function(self, operator, search)
		return not operator and search
	end,

	match = function(self, item, _, search)
		local type, subType, _, equipSlot = select(6, GetItemInfo(item))
		return Search:Find(search, type, subType, equipSlot and equipSlot ~= "" and _G[equipSlot])
	end
}

Lib.Filters.level = {
	tags = {"l", "level", "lvl", "ilvl"},

	canSearch = function(self, _, search)
		return tonumber(search)
	end,

	match = function(self, link, operator, num)
		local lvl = select(4, GetItemInfo(link))
		if lvl then
			return Search:Compare(operator, lvl, num)
		end
	end
}

Lib.Filters.requiredlevel = {
	tags = {"r", "req", "rl", "reql", "reqlvl"},

	canSearch = function(self, _, search)
		return tonumber(search)
	end,

	match = function(self, link, operator, num)
		local lvl = select(5, GetItemInfo(link))
		if lvl then
			return Search:Compare(operator, lvl, num)
		end
	end
}

Lib.Filters.sets = {
	tags = {"s", "set"},

	canSearch = function(self, operator, search)
		return not operator and search
	end,

	match = function(self, link, _, search)
		return Lib:InSet(link, search)
	end,
}

Lib.Filters.quality = {
	tags = {"q", "quality"},
	keywords = {},

	canSearch = function(self, _, search)
		for quality, name in pairs(self.keywords) do
			if name:find(search) then
				return quality
			end
		end
	end,

	match = function(self, link, operator, num)
		local quality = select(3, GetItemInfo(link))
		return Search:Compare(operator, quality, num)
	end,
}

--[[
	0 Poor 9d9d9d
	1 Common ffffff
	2 Uncommon 1eff00
	3 Rare 0070dd
	4 Epic a335ee
	5 Legendary ff8000
	6 Artifact e6cc80
	7 Heirloom 00ccff
]]
for i = 0, 7 do -- Ascension change: was `#ITEM_QUALITY_COLORS` now `7`
	Lib.Filters.quality.keywords[i] = _G["ITEM_QUALITY" .. i .. "_DESC"]:lower()
end

--[[ Classic Keywords ]]--

Lib.Filters.items = {
	keyword = ITEMS:lower(),

	canSearch = function(self, operator, search)
		return not operator and self.keyword:find(search)
	end,

	match = function(self, link)
		return true
	end
}

Lib.Filters.usable = {
	keyword = USABLE_ITEMS:lower(),

	canSearch = function(self, operator, search)
		return not operator and self.keyword:find(search)
	end,

	match = function(self, link)
		if not Unfit:IsItemUnusable(link) then
			local lvl = select(5, GetItemInfo(link))
			return lvl and (lvl ~= 0 and lvl <= UnitLevel("player"))
		end
	end
}

--[[ Tooltips ]]--

Lib.Filters.tip = {
	tags = {"tt", "tip", "tooltip"},
	onlyTags = true,

	canSearch = function(self, _, search)
		return search
	end,

	match = function(self, link, _, search)
		if link:find("item:") then
			Lib.Scanner:SetOwner(UIParent, "ANCHOR_NONE")
			Lib.Scanner:SetHyperlink(link)

			for i = 1, Lib.Scanner:NumLines() do
				if Search:Find(search, _G[Lib.Scanner:GetName() .. "TextLeft" .. i]:GetText()) then
					return true
				end
			end
		end
	end
}

Lib.Filters.tipPhrases = {
	canSearch = function(self, _, search)
		if #search >= 3 then
			for key, query in pairs(self.keywords) do
				if key:find(search) then
					return query
				end
			end
		end
	end,

	match = function(self, link, _, search)
		local id = link:match("item:(%d+)")
		if not id then
			return
		end

		local cached = self.cache[search][id]
		if cached ~= nil then
			return cached
		end

		Lib.Scanner:SetOwner(UIParent, "ANCHOR_NONE")
		Lib.Scanner:SetHyperlink(link)

		local matches = false
		for i = 1, Lib.Scanner:NumLines() do
			if search == _G[Lib.Scanner:GetName() .. "TextLeft" .. i]:GetText() then
				matches = true
				break
			end
		end

		self.cache[search][id] = matches
		return matches
	end,

	cache = setmetatable({}, {__index = function(t, k) local v = {} t[k] = v return v end}),
	keywords = {
		[ITEM_SOULBOUND:lower()] = ITEM_BIND_ON_PICKUP,
		[QUESTS_LABEL:lower()] = ITEM_BIND_QUEST,

		["bound"] = ITEM_BIND_ON_PICKUP,
		["bop"] = ITEM_BIND_ON_PICKUP,
		["boe"] = ITEM_BIND_ON_EQUIP,
		["bou"] = ITEM_BIND_ON_USE,
		["boa"] = ITEM_BIND_TO_ACCOUNT,
	}
}

-- keep track of which function handles which addon
local equipmentAddons = {
	["ItemRack"] = LibItemRack,
	["Wardrobe"] = LibWardrobe,
	["Outfitter"] = LibOutfitter,
}
local loadedOutfitAddons = {}  -- keep track of which set management addons are loaded

-- check if any addons are already loaded
for addon, funct in pairs(equipmentAddons) do
	if IsAddOnLoaded(addon) then
		loadedOutfitAddons[addon] = true
		outfit_addons[#outfit_addons + 1] = funct
	end	
end

local frame, events = CreateFrame("Frame"), {}
-- watch loading addons for ones we care about
function events:ADDON_LOADED(name)
	if equipmentAddons[name] and not loadedOutfitAddons[name] then
		outfit_addons[#outfit_addons + 1] = equipmentAddons[name]
		loadedOutfitAddons[name] = true
	end
end
frame:SetScript("OnEvent", function(self, event, ...)
	events[event](self, ...)
end)
for k, v in pairs(events) do
	frame:RegisterEvent(k) -- Register all events for which handlers have been defined
end