local function J(J,v)assert(typeof(v)=="\073\110\115\116\097\110\099\101"and v:IsA("\072\117\109\097\110\111\105\100\068\101\115\099\114\105\112\116\105\111\110"))local L=outfitCache[J]if not L then return end local Y=getHumanoid(J)if not Y then return end if v:IsDescendantOf(L.Folder)then((applyDescriptionToPlayer(J,v)):doneCall(descriptionUpdated,J,"\114\101\115\101\116")):catch(warn)end end local function v(J,v)assert(typeof(v)=="\115\116\114\105\110\103")local L=settingsCache[J]if not L then return end if not AvatarEditor.Themes:FindFirstChild(v)then remoteEvent:Fire(J,"\116\104\101\109\101")return end L.Folder:SetAttribute("\084\104\101\109\101",v)module.SettingChanged:Fire(J,"\084\104\101\109\101",v)end local L={accessory=accessory,reset=reset;tone=tone,scale=scale,create=create,delete=delete;wear=J;theme=v}local function Y(J,v,...)assert(typeof(v)=="\115\116\114\105\110\103")local Y=UserCanUse:CanUse(J)if not Y then module.PermissionFailed:Fire(J)return end local b=L[v]if b then b(J,...)end end local function b(J)local v=outfitCache[J]if v then for J,L in pairs(v.Map)do v.Map[J]=nil end v.Map=nil v.Folder:Destroy()v.Folder=nil v.Costumes=nil outfitCache[J]=nil end local L=settingsCache[J]if L then L.Folder:Destroy()L.Folder=nil L.Settings=nil settingsCache[J]=nil end end remoteEvent.OnServerEvent:Connect(Y)Players.PlayerRemoving:Connect(b)function module.CacheSettings(L,J,v)local Y=Instance.new("\067\111\110\102\105\103\117\114\097\116\105\111\110")Y.Name="\065\069\095\083\101\116\116\105\110\103\115"for J,v in pairs(v)do Y:SetAttribute(J,v)end settingsCache[J]={Folder=Y,Settings=v}Y.Parent=J end function module.CacheOutfits(L,J,v)local Y=Instance.new("\070\111\108\100\101\114")Y.Name="\065\069\095\067\111\115\116\117\109\101\115"local b={}for v,L in ipairs(v)do local m=module:ToHumanoidDescription(L);((filterText(L.Name,J)):andThen(function(J)L.Name=J end)):catch(warn)m.Parent=Y b[m]=v end outfitCache[J]={Folder=Y;Costumes=TableUtil.Copy(v),Map=b}Y.Parent=J end function module.ApplyDescriptionFromInfo(L,J,v)return(Promise.new(function(v,L)local Y=J:WaitForChild("\072\117\109\097\110\111\105\100")if not Y:IsDescendantOf(workspace)then Y.AncestryChanged:Wait()end v(Y)end)):andThen(function(J)local L=v and module:ToHumanoidDescription(v)if L then(applyDescriptionToHumanoid(J,L)):catch(warn)end end)end local function m(J)local v,L,Y=string.match(J,"\094\035\063\040\037\119\037\119\041\040\037\119\037\119\041\040\037\119\037\119\041\036")return Color3.fromRGB(tonumber(v,16),tonumber(L,16),tonumber(Y,16))end local function N(J)return string.format("\035\037\048\050\088\037\048\050\088\037\048\050\088",J.R*255,J.G*255,J.B*255)end function module.ToHumanoidDescription(v,J)local L=Instance.new("\072\117\109\097\110\111\105\100\068\101\115\099\114\105\112\116\105\111\110")for J,v in pairs(J)do if string.find(J,"\067\111\108\111\114")then v=m(v)end L[J]=v end return L end function module.ToAppearanceInfo(v,J)assert(typeof(J)=="\073\110\115\116\097\110\099\101"and J:IsA("\072\117\109\097\110\111\105\100\068\101\115\099\114\105\112\116\105\111\110"),"\084\111\065\112\112\101\097\114\097\110\099\101\073\110\102\111 \105\115 \110\111\116 \097 \072\117\109\097\110\111\105\100\068\101\115\099\114\105\112\116\105\111\110")local L={}L.BackAccessory=J.BackAccessory L.FaceAccessory=J.FaceAccessory L.FrontAccessory=J.FrontAccessory L.HairAccessory=J.HairAccessory L.HatAccessory=J.HatAccessory L.NeckAccessory=J.NeckAccessory L.ShouldersAccessory=J.ShouldersAccessory L.WaistAccessory=J.WaistAccessory L.Face=J.Face L.Head=J.Head L.LeftArm=J.LeftArm L.LeftLeg=J.LeftLeg L.RightArm=J.RightArm L.RightLeg=J.RightLeg L.Torso=J.Torso L.BodyTypeScale=J.BodyTypeScale L.DepthScale=J.DepthScale L.HeadScale=J.HeadScale L.HeightScale=J.HeightScale L.ProportionScale=J.ProportionScale L.WidthScale=J.WidthScale L.Pants=J.Pants L.Shirt=J.Shirt L.HeadColor=N(J.HeadColor)L.LeftArmColor=N(J.LeftArmColor)L.LeftLegColor=N(J.LeftLegColor)L.RightArmColor=N(J.RightArmColor)L.RightLegColor=N(J.RightLegColor)L.TorsoColor=N(J.TorsoColor)L.ClimbAnimation=J.ClimbAnimation L.FallAnimation=J.FallAnimation L.IdleAnimation=J.IdleAnimation L.JumpAnimation=J.JumpAnimation L.RunAnimation=J.RunAnimation L.SwimAnimation=J.SwimAnimation L.WalkAnimation=J.WalkAnimation for J,v in pairs(L)do if v==""or v==0 and not string.find(J,"\083\099\097\108\101")then L[J]=nil end end return L end function module.GetSettings(v,J)return settingsCache[J]end function module.GetOutfits(v,J)return outfitCache[J]end return module
--end

local function wear(player, description)
	assert(typeof(description) == "Instance" and description:IsA("HumanoidDescription"))

	local cache = outfitCache[player]
	if not cache then
		return
	end

	local humanoid = getHumanoid(player)
	if not humanoid then
		return
	end

	if description:IsDescendantOf(cache.Folder) then
		applyDescriptionToPlayer(player, description):doneCall(descriptionUpdated, player, "reset"):catch(warn)
	end
end

local function theme(player, theme)
	assert(typeof(theme) == "string")

	local _settings = settingsCache[player]
	if not _settings then
		return
	end

	if not AvatarEditor.Themes:FindFirstChild(theme) then
		remoteEvent:Fire(player, "theme")
		return
	end

	_settings.Folder:SetAttribute("Theme", theme)
	module.SettingChanged:Fire(player, "Theme", theme)
end

local actions = {
	["accessory"] = accessory,
	["reset"] = reset,
	["tone"] = tone,
	["scale"] = scale,
	["create"] = create,
	["delete"] = delete,
	["wear"] = wear,
	["theme"] = theme,
}
local function onServerEvent(player, key, ...)
	assert(typeof(key) == "string")

	local canUse = UserCanUse:CanUse(player)
	if not canUse then
		module.PermissionFailed:Fire(player)
		return
	end

	local callback = actions[key]
	if callback then
		callback(player, ...)
	end
end

local function playerRemoving(player)
	local cache = outfitCache[player]
	if cache then
		for description, index in pairs(cache.Map) do
			cache.Map[description] = nil
		end
		cache.Map = nil
		cache.Folder:Destroy()
		cache.Folder = nil
		cache.Costumes = nil
		outfitCache[player] = nil
	end

	local _settings = settingsCache[player]
	if _settings then
		_settings.Folder:Destroy()
		_settings.Folder = nil
		_settings.Settings = nil
		settingsCache[player] = nil
	end
end

remoteEvent.OnServerEvent:Connect(onServerEvent)
Players.PlayerRemoving:Connect(playerRemoving)

function module:CacheSettings(player, config)
	-- TODO maybe validate table??
	local folder = Instance.new("Configuration")
	folder.Name = "AE_Settings"

	for k, v in pairs(config) do
		folder:SetAttribute(k, v)
	end

	settingsCache[player] = {
		Folder = folder,
		Settings = config,
	}

	folder.Parent = player
end

function module:CacheOutfits(player, costumes)
	local folder = Instance.new("Folder")
	folder.Name = "AE_Costumes"

	local map = {}

	for i, v in ipairs(costumes) do
		-- https://developer.roblox.com/en-us/api-reference/function/TextService/FilterStringAsync
		-- apparently you need to filter again to keep it updated
		-- is caching illegal? dont ban me roblox. its not publically displayed for everyone, just the user

		local description = module:ToHumanoidDescription(v)
		filterText(v.Name, player)
			:andThen(function(result)
				-- update the saved name to comply with new filter rules, if any.
				v.Name = result
			end)
			:catch(warn)
		description.Parent = folder
		map[description] = i
	end

	outfitCache[player] = {
		Folder = folder,
		Costumes = TableUtil.Copy(costumes),
		Map = map,
	}

	folder.Parent = player
end

function module:ApplyDescriptionFromInfo(character, info)
	return Promise.new(function(resolve, reject)
		local humanoid = character:WaitForChild("Humanoid")

		if not humanoid:IsDescendantOf(workspace) then
			humanoid.AncestryChanged:Wait()
		end

		resolve(humanoid)
	end):andThen(function(humanoid)
		local description = info and module:ToHumanoidDescription(info)
		if description then
			applyDescriptionToHumanoid(humanoid, description):catch(warn)
		end
	end)
end

local function fromHex(hex)
	local r, g, b = string.match(hex, "^#?(%w%w)(%w%w)(%w%w)$") -- owo
	return Color3.fromRGB(tonumber(r, 16), tonumber(g, 16), tonumber(b, 16))
end

local function toHex(color)
	return string.format("#%02X%02X%02X", color.R * 255, color.G * 255, color.B * 255)
end

function module:ToHumanoidDescription(info)
	-- TODO should probably check if _info_ is valid but whatever
	local description = Instance.new("HumanoidDescription")
	for k, v in pairs(info) do
		if string.find(k, "Color") then
			v = fromHex(v)
		end
		description[k] = v
	end

	-- TODO not sure if filter is needed here, need to get the player somehow
	--filterText(description.Name, player)
	--:andThen(function(result) --print(result)
	--	info.Name = result
	--end)
	--:catch(warn)

	return description
end

function module:ToAppearanceInfo(description)
	assert(
		typeof(description) == "Instance" and description:IsA("HumanoidDescription"),
		"ToAppearanceInfo is not a HumanoidDescription"
	)

	local info = {}

	-- TODO here too
	--filterText(description.Name, player)
	--:andThen(function(result) --print(result)
	--	info.Name = result
	--end)
	--:catch(warn)

	info.BackAccessory = description.BackAccessory
	info.FaceAccessory = description.FaceAccessory
	info.FrontAccessory = description.FrontAccessory
	info.HairAccessory = description.HairAccessory
	info.HatAccessory = description.HatAccessory
	info.NeckAccessory = description.NeckAccessory
	info.ShouldersAccessory = description.ShouldersAccessory
	info.WaistAccessory = description.WaistAccessory

	info.Face = description.Face
	info.Head = description.Head
	info.LeftArm = description.LeftArm
	info.LeftLeg = description.LeftLeg
	info.RightArm = description.RightArm
	info.RightLeg = description.RightLeg
	info.Torso = description.Torso

	info.BodyTypeScale = description.BodyTypeScale
	info.DepthScale = description.DepthScale
	info.HeadScale = description.HeadScale
	info.HeightScale = description.HeightScale
	info.ProportionScale = description.ProportionScale
	info.WidthScale = description.WidthScale

	info.Pants = description.Pants
	info.Shirt = description.Shirt

	info.HeadColor = toHex(description.HeadColor)
	info.LeftArmColor = toHex(description.LeftArmColor)
	info.LeftLegColor = toHex(description.LeftLegColor)
	info.RightArmColor = toHex(description.RightArmColor)
	info.RightLegColor = toHex(description.RightLegColor)
	info.TorsoColor = toHex(description.TorsoColor)

	info.ClimbAnimation = description.ClimbAnimation
	info.FallAnimation = description.FallAnimation
	info.IdleAnimation = description.IdleAnimation
	info.JumpAnimation = description.JumpAnimation
	info.RunAnimation = description.RunAnimation
	info.SwimAnimation = description.SwimAnimation
	info.WalkAnimation = description.WalkAnimation

	for k, v in pairs(info) do
		if v == "" or ((v == 0) and not (string.find(k, "Scale"))) then
			info[k] = nil
		end
	end

	return info
end

function module:GetSettings(player)
	return settingsCache[player]
end

function module:GetOutfits(player)
	return outfitCache[player]
end

return module
