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



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
