local D=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local R=D.AvatarEditor local j=require(R.Shared.QueryMap)local Y=require(R.Shared.Util.Signal)local f=require(R.Shared.Util.Maid)local A=require(R.Shared.Settings)local P=require(R.Shared.Cache)local T=require(R.Client.Theme)local e=(require(R.Gui.ButtonTemplateToLua))()local k=P.new(A.CACHE_SIZE)local N={}N.__index=N local function W(D)local R={}table.move(D,1,#D,1,R)return R end local function B(D,R,Y,f)local A=string.format("\037\115\044 \037\115\044 \037\115\044 \037\115",j.GetOptionName("\065\115\115\101\116\084\121\112\101",R),tostring(Y),f,D)return A end local function Z(D,R,j,Y)local f=B(D,R,j,Y)return k:Get(f)end local function s(D,R,j,Y,f)local A=B(D,R,j,Y)f=W(f)k:Set(A,f)end local function h(D)table.sort(D,function(D,R)return D._name<R._name end)end function stringMatch(D,R)local j=1 local Y=#R local f=1 local A=#D while j<=Y and f<=A do local Y=(R:sub(j,j)):lower()local A=(D:sub(f,f)):lower()if Y==A then j=j+1 end f=f+1 end return Y>0 and(A>0 and j-1==Y)end local function g()local D=e:Clone()return D end local function J(D,R)return D[R]end function N.new(D,R,j,P)local e=setmetatable({},N)e.ScrollingFrame=D e.All=R e.List=R e.Wearing=j e.PageSize=A.PAGE_SIZE e.MaxPageSize=A.PAGE_SIZE e.GridLayout=D.UIGridLayout e.Maid=f.new()e.Listeners=f.new()e.Children={}e.Pool={}e.State=P e.ItemSelected=Y.new()e.ViewDetails=Y.new()for D=1,50,1 do e.Pool[D]=g()end e.Maid:GiveTask(T.Changed:Connect(function()e:Render()end))e.Maid:GiveTask(T:Bind(D,"\083\099\114\111\108\108\066\097\114\073\109\097\103\101\067\111\108\111\114\051","\083\099\114\111\108\108\098\097\114"))e.Maid:GiveTask((e.ScrollingFrame:GetPropertyChangedSignal("\067\097\110\118\097\115\080\111\115\105\116\105\111\110")):Connect(function()local R=e.PageSize local j=e.GridLayout local Y=D.CanvasPosition.Y>=(j.AbsoluteContentSize.Y-D.AbsoluteSize.Y)-10 if Y then local D=math.min(R+A.PAGE_SIZE,e.MaxPageSize)if D~=R then e.PageSize=D e:Update()end end end))for D,R in ipairs(e.All)do R._name=R.name:lower()R._wearing=J(e.Wearing,R.id)end return e end function N.GetItem(D)local R=D.Pool local j=table.remove(R,#R)if not j then j=g()end return j end function N.Render(D)local R=D.Listeners local j=D.List local Y=D.Children local f=D.ScrollingFrame local P=D.GridLayout local e=D.Pool local k=table.create(1000)R:DoCleaning()for j,P in ipairs(j)do local e=Y[j]or D:GetItem()e.LayoutOrder=j e.ItemLabel.Text=P.name e.Icon.Image="\114\098\120\116\104\117\109\098\058\047\047\116\121\112\101\061\065\115\115\101\116\038\105\100\061"..(P.id.."\038\119\061\049\053\048\038\104\061\049\053\048")e.ItemLabel.Visible=false e.Wearing.Visible=P._wearing e.More.Visible=false k[j]=e R:GiveTask(T:Bind(e,"\066\097\099\107\103\114\111\117\110\100\067\111\108\111\114\051","\066\117\116\116\111\110"))R:GiveTask(T:Bind(e.ItemLabel,"\084\101\120\116\067\111\108\111\114\051","\084\101\120\116"))R:GiveTask(T:Bind(e.ItemLabel,"\084\101\120\116\083\116\114\111\107\101\067\111\108\111\114\051","\084\101\120\116\083\116\114\111\107\101"))R:GiveTask(T:Bind(e.More,"\066\097\099\107\103\114\111\117\110\100\067\111\108\111\114\051","\068\101\116\097\105\108\115"))R:GiveTask(T:Bind(e.More,"\084\101\120\116\067\111\108\111\114\051","\084\101\120\116"))R:GiveTask(T:Bind(e.Wearing,"\066\097\099\107\103\114\111\117\110\100\067\111\108\111\114\051","\087\101\097\114\105\110\103"))R:GiveTask(e.More.Activated:Connect(function(R)D.ViewDetails:Fire(P)end))R:GiveTask(e.TouchLongPress:Connect(function(R,j)e.ItemLabel.Visible=false e.More.Visible=false D.ViewDetails:Fire(P)end))R:GiveTask(e.Activated:Connect(function(R)D.ItemSelected:Fire(P,P._wearing)end))R:GiveTask(e.InputBegan:Connect(function(D)if not A.HOVER_INPUTS[D.UserInputType]then return end e.ItemLabel.Visible=true if D.UserInputType==Enum.UserInputType.MouseMovement then e.More.Visible=true end end))R:GiveTask(e.InputEnded:Connect(function(D)if not A.HOVER_INPUTS[D.UserInputType]then return end e.ItemLabel.Visible=false e.More.Visible=false end))e.Parent=f end for D=#j+1,#Y,1 do local R=Y[D]R.Parent=nil table.insert(e,R)end D.Children=k f.CanvasSize=UDim2.new(0,0,0,P.AbsoluteContentSize.Y)end function N.Search(A,D,R,j,Y,f)local P=table.create(10000)if R==""then if Y then for D,R in ipairs(D)do if R._wearing then table.insert(P,R)end end else for D,R in ipairs(D)do if R.assetType==j then table.insert(P,R)end end end else if Y then for D,j in ipairs(D)do if stringMatch(j._name,R)and j._wearing then table.insert(P,j)end end else for D,Y in ipairs(D)do if stringMatch(Y._name,R)and Y.assetType==j then table.insert(P,Y)end end end end return P end function N.Update(R,D)local j=R.ScrollingFrame local Y=R.State if D then R.PageSize=A.PAGE_SIZE R.MaxPageSize=A.PAGE_SIZE j.CanvasPosition=Vector2.new(0,0)end local f={}local P=Z(Y.search,Y.assetType,Y.wearing,Y.uid)if P then R.MaxPageSize=#P for D=1,R.PageSize,1 do table.insert(f,D,P[D])end s(Y.search,Y.assetType,Y.wearing,Y.uid,P)else local D=R:Search(R.All,Y.search,Y.assetType,Y.wearing,Y.uid)h(D)R.MaxPageSize=#D for R=1,R.PageSize,1 do table.insert(f,R,D[R])end s(Y.search,Y.assetType,Y.wearing,Y.uid,D)end R.List=f R:Render()end function N.UpdateWearing(R,D)R.Wearing=D for D,j in ipairs(R.All)do j._wearing=J(R.Wearing,j.id)end R:Render()end function N.Destroy(D)D.ScrollingFrame.CanvasPosition=Vector2.new(0,0)D.ScrollingFrame=nil D.All=nil D.List=nil D.Wearing=nil D.PageSize=nil D.MaxPageSize=nil D.GridLayout=nil D.Maid:DoCleaning()D.Maid=nil D.Listeners:DoCleaning()D.Listeners=nil for D,R in ipairs(D.Children)do R:Destroy()end table.clear(D.Children)for D,R in ipairs(D.Pool)do R:Destroy()end table.clear(D.Pool)D.State=nil D.ItemSelected:Destroy()D.ItemSelected=nil D.ViewDetails:Destroy()D.ViewDetails=nil end return N


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local AvatarEditor = ReplicatedStorage.AvatarEditor
local QueryMap = require(AvatarEditor.Shared.QueryMap)
local Signal = require(AvatarEditor.Shared.Util.Signal)
local Maid = require(AvatarEditor.Shared.Util.Maid)
local Settings = require(AvatarEditor.Shared.Settings)
local Cache = require(AvatarEditor.Shared.Cache)
local Theme = require(AvatarEditor.Client.Theme)

local buttonTemplate = require(AvatarEditor.Gui.ButtonTemplateToLua)()

local cache = Cache.new(Settings.CACHE_SIZE)

local Class = {}
Class.__index = Class


local function copy(array)
	local new = {}
	table.move(array, 1, #array, 1, new)
	return new
end


local function getCacheKey(pattern, assetType, wearing, uid)
	local key = string.format("%s, %s, %s, %s", QueryMap.GetOptionName("AssetType", assetType), tostring(wearing), uid, pattern)
	return key
end


local function getFromCache(pattern, assetType, wearing, uid)
	local key = getCacheKey(pattern, assetType, wearing, uid)
	return cache:Get(key)
end


local function createCache(pattern, assetType, wearing, uid, list)
	local key = getCacheKey(pattern, assetType, wearing, uid)
	list = copy(list)
	cache:Set(key, list)
end


local function sort(list)
	table.sort(list, function(a, b)
		return a._name < b._name
	end)
end


function stringMatch(str, pattern)
	local patternIndex = 1
	local patternLength = #pattern
	local strIndex = 1
	local strLength = #str

	while patternIndex <= patternLength and strIndex <= strLength do
		local patternChar = pattern:sub(patternIndex, patternIndex):lower()
		local strChar = str:sub(strIndex, strIndex):lower()

		if patternChar == strChar then
			patternIndex = patternIndex + 1
		end

		strIndex = strIndex + 1
	end

	return patternLength > 0 and strLength > 0 and (patternIndex - 1) == patternLength
end


local function createButton()
	local button = buttonTemplate:Clone()
	return button
end


local function isWearing(assets, id)
	return assets[id]
end


function Class.new(scrollingFrame, catalogData, wearingAssets, searchState)
	local self = setmetatable({}, Class)
	
	self.ScrollingFrame = scrollingFrame
	self.All = catalogData
	self.List = catalogData
	self.Wearing = wearingAssets
	self.PageSize = Settings.PAGE_SIZE
	self.MaxPageSize = Settings.PAGE_SIZE
	self.GridLayout = scrollingFrame.UIGridLayout
	self.Maid = Maid.new()
	self.Listeners = Maid.new()
	self.Children = {}
	self.Pool = {}
	self.State = searchState
	self.ItemSelected = Signal.new()
	self.ViewDetails = Signal.new()
	
	for i = 1, 50 do
		self.Pool[i] = createButton()
	end
	
	self.Maid:GiveTask(Theme.Changed:Connect(function()
		self:Render()
	end))
	
	self.Maid:GiveTask(Theme:Bind(scrollingFrame, "ScrollBarImageColor3", "Scrollbar"))
	
	self.Maid:GiveTask(self.ScrollingFrame:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
		local currentPageSize = self.PageSize
		local uiGridLayout = self.GridLayout
		-- i think indexing AbsoluteContentSize is really intensive especially on larger AbsoluteContentSize
		local scrollEnough = scrollingFrame.CanvasPosition.Y >= uiGridLayout.AbsoluteContentSize.Y - scrollingFrame.AbsoluteSize.Y - 10
		if scrollEnough then
			local newPageSize = math.min(currentPageSize + Settings.PAGE_SIZE, self.MaxPageSize)
			if newPageSize ~= currentPageSize then
				self.PageSize = newPageSize
				self:Update()
			end
		end
	end))
	
	for i, v in ipairs(self.All) do
		v._name = v.name:lower()
		v._wearing = isWearing(self.Wearing, v.id)
	end
	
	return self
end


function Class:GetItem()
	local pool = self.Pool
	local button = table.remove(pool, #pool)
	if not button then
		button = createButton()
	end
	return button
end


function Class:Render()
	local maid = self.Listeners
	local catalogList = self.List
	local children = self.Children
	local scrollingFrame = self.ScrollingFrame
	local uiGridLayout = self.GridLayout
	local pool = self.Pool
	local newChildren = table.create(1000)
	
	maid:DoCleaning()

	for i, v in ipairs(catalogList) do
		local button = children[i] or self:GetItem()
		button.LayoutOrder = i
		button.ItemLabel.Text = v.name
		button.Icon.Image = "rbxthumb://type=Asset&id=" .. v.id .. "&w=150&h=150"
		button.ItemLabel.Visible = false
		button.Wearing.Visible = v._wearing
		button.More.Visible = false
		newChildren[i] = button
		
		maid:GiveTask(Theme:Bind(button, "BackgroundColor3", "Button"))
		maid:GiveTask(Theme:Bind(button.ItemLabel, "TextColor3", "Text"))
		maid:GiveTask(Theme:Bind(button.ItemLabel, "TextStrokeColor3", "TextStroke"))
		maid:GiveTask(Theme:Bind(button.More, "BackgroundColor3", "Details"))
		maid:GiveTask(Theme:Bind(button.More, "TextColor3", "Text"))
		maid:GiveTask(Theme:Bind(button.Wearing, "BackgroundColor3", "Wearing"))
		
		maid:GiveTask(button.More.Activated:Connect(function(inputObject)
			self.ViewDetails:Fire(v)
		end))
		
		maid:GiveTask(button.TouchLongPress:Connect(function(touchPositions, inputState)
			button.ItemLabel.Visible = false
			button.More.Visible = false
			self.ViewDetails:Fire(v)
		end))

		maid:GiveTask(button.Activated:Connect(function(inputObject)
			self.ItemSelected:Fire(v, v._wearing)
		end))

		maid:GiveTask(button.InputBegan:Connect(function(inputObject)
			if not Settings.HOVER_INPUTS[inputObject.UserInputType] then
				return
			end

			button.ItemLabel.Visible = true
			if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
				button.More.Visible = true
			end
		end))

		maid:GiveTask(button.InputEnded:Connect(function(inputObject)
			if not Settings.HOVER_INPUTS[inputObject.UserInputType] then
				return
			end

			button.ItemLabel.Visible = false
			button.More.Visible = false
		end))
		
		button.Parent = scrollingFrame
	end

	for i = #catalogList + 1, #children do
		local button = children[i]
		button.Parent = nil
		table.insert(pool, button)
	end
	
	self.Children = newChildren
	scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, uiGridLayout.AbsoluteContentSize.Y)
end


function Class:Search(list, pattern, assetType, wearing, uid)
	local results = table.create(10000)
	-- umm yeaaa
	if pattern == "" then
		if wearing then
			for i, v in ipairs(list) do
				if v._wearing then
					table.insert(results, v)
				end
			end
		else
			for i, v in ipairs(list) do
				if v.assetType == assetType then
					table.insert(results, v)
				end
			end
		end
	else
		if wearing then
			for i, v in ipairs(list) do
				if stringMatch(v._name, pattern) and v._wearing then
					table.insert(results, v)
				end
			end
		else
			for i, v in ipairs(list) do
				if stringMatch(v._name, pattern) and v.assetType == assetType then
					table.insert(results, v)
				end
			end
		end
	end
	
	return results
end


function Class:Update(reset)
	local scrollingFrame = self.ScrollingFrame
	local state = self.State

	if reset then
		self.PageSize = Settings.PAGE_SIZE
		self.MaxPageSize = Settings.PAGE_SIZE
		scrollingFrame.CanvasPosition = Vector2.new(0, 0)
	end

	local filteredItems = {}
	local cacheResults = getFromCache(state.search, state.assetType, state.wearing, state.uid)
	if cacheResults then
		self.MaxPageSize = #cacheResults
		
		for i = 1, self.PageSize do
			table.insert(filteredItems, i, cacheResults[i])
		end
		
		createCache(state.search, state.assetType, state.wearing, state.uid, cacheResults)
	else
		local searchResults = self:Search(self.All, state.search, state.assetType, state.wearing, state.uid)
		sort(searchResults)
		
		self.MaxPageSize = #searchResults
		
		for i = 1, self.PageSize do
			table.insert(filteredItems, i, searchResults[i])
		end
		
		createCache(state.search, state.assetType, state.wearing, state.uid, searchResults)
	end

	self.List = filteredItems
	self:Render()
end


function Class:UpdateWearing(wearingAssets)
	self.Wearing = wearingAssets
	for i, v in ipairs(self.All) do
		v._wearing = isWearing(self.Wearing, v.id)
	end
	self:Render()
end


function Class:Destroy()
	self.ScrollingFrame.CanvasPosition = Vector2.new(0, 0)
	self.ScrollingFrame = nil
	self.All = nil
	self.List = nil
	self.Wearing = nil
	self.PageSize = nil
	self.MaxPageSize = nil
	self.GridLayout = nil
	self.Maid:DoCleaning()
	self.Maid = nil
	self.Listeners:DoCleaning()
	self.Listeners = nil
	for i, v in ipairs(self.Children) do
		v:Destroy()
	end
	table.clear(self.Children)
	for i, v in ipairs(self.Pool) do
		v:Destroy()
	end
	table.clear(self.Pool)
	self.State = nil
	self.ItemSelected:Destroy()
	self.ItemSelected = nil
	self.ViewDetails:Destroy()
	self.ViewDetails = nil
end


return Class
