local function T(T,g,b,R)local G local Y=false local a,C=pcall(function()if b.WipeProfile~=true then local Y=function(T)if T=="\080\082\079\070\073\076\069\095\087\073\080\069\068"then T=nil end local g=false local R=false local G={0,{}}if T==nil then g=true elseif type(T)~="\116\097\098\108\101"then g=true R=true end if type(T)=="\116\097\098\108\101"then if type(T.Data)=="\116\097\098\108\101"and(type(T.MetaData)=="\116\097\098\108\101"and type(T.GlobalUpdates)=="\116\097\098\108\101")then T.WasCorrupted=false G=T.GlobalUpdates if b.ExistingProfileHandle~=nil then b.ExistingProfileHandle(T)end elseif T.Data==nil and(T.MetaData==nil and type(T.GlobalUpdates)=="\116\097\098\108\101")then T.WasCorrupted=false G=T.GlobalUpdates g=true else g=true R=true end end if g==true then T={GlobalUpdates=G}if b.MissingProfileHandle~=nil then b.MissingProfileHandle(T)end end if b.EditProfile~=nil then b.EditProfile(T)end if R==true then T.WasCorrupted=true end return T end if R==true then G=MockUpdateAsync(UserMockDataStore,T._profile_store_name,g,Y)Madwork.HeartbeatWait()elseif UseMockDataStore==true then G=MockUpdateAsync(MockDataStore,T._profile_store_name,g,Y)Madwork.HeartbeatWait()else G=T._global_data_store:UpdateAsync(g,Y)end else if R==true then local b=UserMockDataStore[T._profile_store_name]if b~=nil then b[g]=nil end Y=true Madwork.HeartbeatWait()elseif UseMockDataStore==true then local b=MockDataStore[T._profile_store_name]if b~=nil then b[g]=nil end Y=true Madwork.HeartbeatWait()else G=T._global_data_store:UpdateAsync(g,function()return"\080\082\079\070\073\076\069\095\087\073\080\069\068"end)if G=="\080\082\079\070\073\076\069\095\087\073\080\069\068"then Y=true end end end end)if b.WipeProfile==true then return Y elseif a==true and type(G)=="\116\097\098\108\101"then if G.WasCorrupted==true then RegisterCorruption(T._profile_store_name,g)end return G else RegisterIssue(C~=nil and C or"\085\110\100\101\102\105\110\101\100 \101\114\114\111\114",T._profile_store_name,g)return nil end end local function g(T)local g=table.find(AutoSaveList,T)if g~=nil then table.remove(AutoSaveList,g)if g<AutoSaveIndex then AutoSaveIndex=AutoSaveIndex-1 end if AutoSaveList[AutoSaveIndex]==nil then AutoSaveIndex=1 end end end local function b(T)table.insert(AutoSaveList,AutoSaveIndex,T)if#AutoSaveList>1 then AutoSaveIndex=AutoSaveIndex+1 elseif#AutoSaveList==1 then LastAutoSave=os.clock()end end local function R(T)local b=T._profile_store local R=T._is_user_mock==true and b._mock_loaded_profiles or b._loaded_profiles R[T._profile_key]=nil if next(b._loaded_profiles)==nil and next(b._mock_loaded_profiles)==nil then local T=table.find(ActiveProfileStores,b)if T~=nil then table.remove(ActiveProfileStores,T)end end g(T)local G local Y local a=T.MetaData.ActiveSession if a~=nil then G=a[1]Y=a[2]end for T,g in ipairs(T._release_listeners)do g(G,Y)end T._release_listeners={}end local function G(T,g,b)local R=T.GlobalUpdates local G=R._pending_update_lock local Y=R._pending_update_clear for T,b in ipairs(b[2])do local a for T,g in ipairs(g[2])do if g[1]==b[1]then a=g break end end local C=false if a==nil then C=true elseif b[2]>a[2]or b[3]~=a[3]then C=true end if C==true then if b[3]==false then local T=false for g,R in ipairs(G)do if b[1]==R then T=true break end end if T==false then for T,g in ipairs(R._new_active_update_listeners)do g(b[1],b[4])end end end if b[3]==true then local T=false for g,R in ipairs(Y)do if b[1]==R then T=true break end end if T==false then for g,R in ipairs(R._new_locked_update_listeners)do R(b[1],b[4])for g,R in ipairs(Y)do if b[1]==R then T=true break end end if T==true then break end end end end end end end local function Y(g,b)if type(g.Data)~="\116\097\098\108\101"then RegisterCorruption(g._profile_store._profile_store_name,g._profile_key)error("\091\080\114\111\102\105\108\101\083\101\114\118\105\099\101\093\058 \080\082\079\070\073\076\069 \068\065\084\065 \067\079\082\082\085\080\084\069\068 \068\085\082\073\078\071 \082\085\078\084\073\077\069\033 \080\114\111\102\105\108\101\083\116\111\114\101 \061 \""..(g._profile_store._profile_store_name..("\"\044 \075\101\121 \061 \""..(g._profile_key.."\""))))end if b==true then R(g)end ActiveProfileSaveJobs=ActiveProfileSaveJobs+1 local Y=g.MetaData.SessionLoadCount local a=true while a==true do if b~=true then a=false end local C=T(g._profile_store,g._profile_key,{ExistingProfileHandle=nil,MissingProfileHandle=nil;EditProfile=function(T)local R=T.MetaData.ActiveSession local G=T.MetaData.ForceLoadSession local a=T.MetaData.SessionLoadCount local C=false local v=false if type(R)=="\116\097\098\108\101"then C=IsThisSession(R)and a==Y end if type(G)=="\116\097\098\108\101"then v=not IsThisSession(G)end if C==true then local R=T.GlobalUpdates local G=R[2]local Y=g.GlobalUpdates local a=Y._pending_update_lock local C=Y._pending_update_clear for T=1,#G,1 do for g,b in ipairs(a)do if G[T][1]==b then G[T][3]=true break end end end for T,g in ipairs(C)do for T=1,#G,1 do if G[T][1]==g and G[T][3]==true then table.remove(G,T)break end end end T.Data=g.Data T.MetaData.MetaTags=g.MetaData.MetaTags T.MetaData.LastUpdate=os.time()if b==true or v==true then T.MetaData.ActiveSession=nil end end end},g._is_user_mock)if C~=nil then a=false local T=g.GlobalUpdates local b=T._updates_latest local v=C.GlobalUpdates T._updates_latest=v local K=g.MetaData.MetaTags g.MetaData=C.MetaData g.MetaData.MetaTagsLatest=g.MetaData.MetaTags g.MetaData.MetaTags=K local t=C.MetaData.ActiveSession local d=C.MetaData.ForceLoadSession local q=C.MetaData.SessionLoadCount local Q=false if type(t)=="\116\097\098\108\101"then Q=IsThisSession(t)and q==Y end local m=g:IsActive()if Q==true then if m==true then G(g,b,v)end else if m==true then R(g)end end elseif a==true then Madwork.HeartbeatWait()end end ActiveProfileSaveJobs=ActiveProfileSaveJobs-1 end

local function StandardProfileUpdateAsyncDataStore(profile_store, profile_key, update_settings, is_user_mock)
	local loaded_data
	local wipe_status = false
	local success, error_message = pcall(function()
		if update_settings.WipeProfile ~= true then
			local transform_function = function(latest_data)
				if latest_data == "PROFILE_WIPED" then
					latest_data = nil -- Profile was previously wiped - ProfileService will act like it was empty
				end

				local missing_profile = false
				local data_corrupted = false
				local global_updates_data = {0, {}}

				if latest_data == nil then
					missing_profile = true
				elseif type(latest_data) ~= "table" then
					missing_profile = true
					data_corrupted = true
				end

				if type(latest_data) == "table" then
					-- Case #1: Profile was loaded
					if type(latest_data.Data) == "table" and
						type(latest_data.MetaData) == "table" and
						type(latest_data.GlobalUpdates) == "table" then

						latest_data.WasCorrupted = false -- Must be set to false if set previously
						global_updates_data = latest_data.GlobalUpdates
						if update_settings.ExistingProfileHandle ~= nil then
							update_settings.ExistingProfileHandle(latest_data)
						end
						-- Case #2: Profile was not loaded but GlobalUpdate data exists
					elseif latest_data.Data == nil and
						latest_data.MetaData == nil and
						type(latest_data.GlobalUpdates) == "table" then

						latest_data.WasCorrupted = false -- Must be set to false if set previously
						global_updates_data = latest_data.GlobalUpdates
						missing_profile = true
					else
						missing_profile = true
						data_corrupted = true
					end
				end

				-- Case #3: Profile was not created or corrupted and no GlobalUpdate data exists
				if missing_profile == true then
					latest_data = {
						-- Data = nil,
						-- MetaData = nil,
						GlobalUpdates = global_updates_data,
					}
					if update_settings.MissingProfileHandle ~= nil then
						update_settings.MissingProfileHandle(latest_data)
					end
				end

				-- Editing profile:
				if update_settings.EditProfile ~= nil then
					update_settings.EditProfile(latest_data)
				end

				-- Data corruption handling (Silently override with empty profile) (Also run Case #1)
				if data_corrupted == true then
					latest_data.WasCorrupted = true -- Temporary tag that will be removed on first save
				end

				return latest_data
			end
			if is_user_mock == true then -- Used when the profile is accessed through ProfileStore.Mock
				loaded_data = MockUpdateAsync(UserMockDataStore, profile_store._profile_store_name, profile_key, transform_function)
				Madwork.HeartbeatWait() -- Simulate API call yield
			elseif UseMockDataStore == true then -- Used when API access is disabled
				loaded_data = MockUpdateAsync(MockDataStore, profile_store._profile_store_name, profile_key, transform_function)
				Madwork.HeartbeatWait() -- Simulate API call yield
			else
				loaded_data = profile_store._global_data_store:UpdateAsync(profile_key, transform_function)
			end
		else
			if is_user_mock == true then -- Used when the profile is accessed through ProfileStore.Mock
				local mock_data_store = UserMockDataStore[profile_store._profile_store_name]
				if mock_data_store ~= nil then
					mock_data_store[profile_key] = nil
				end
				wipe_status = true
				Madwork.HeartbeatWait() -- Simulate API call yield
			elseif UseMockDataStore == true then -- Used when API access is disabled
				local mock_data_store = MockDataStore[profile_store._profile_store_name]
				if mock_data_store ~= nil then
					mock_data_store[profile_key] = nil
				end
				wipe_status = true
				Madwork.HeartbeatWait() -- Simulate API call yield
			else
				loaded_data = profile_store._global_data_store:UpdateAsync(profile_key, function()
					return "PROFILE_WIPED" -- It's impossible to set DataStore keys to nil after they have been set
				end)
				if loaded_data == "PROFILE_WIPED" then
					wipe_status = true
				end
			end
		end
	end)
	if update_settings.WipeProfile == true then
		return wipe_status
	elseif success == true and type(loaded_data) == "table" then
		-- Corruption handling:
		if loaded_data.WasCorrupted == true then
			RegisterCorruption(profile_store._profile_store_name, profile_key)
		end
		-- Return loaded_data:
		return loaded_data
	else
		RegisterIssue((error_message ~= nil) and error_message or "Undefined error", profile_store._profile_store_name, profile_key)
		-- Return nothing:
		return nil
	end
end

local function RemoveProfileFromAutoSave(profile)
	local auto_save_index = table.find(AutoSaveList, profile)
	if auto_save_index ~= nil then
		table.remove(AutoSaveList, auto_save_index)
		if auto_save_index < AutoSaveIndex then
			AutoSaveIndex = AutoSaveIndex - 1 -- Table contents were moved left before AutoSaveIndex so move AutoSaveIndex left as well
		end
		if AutoSaveList[AutoSaveIndex] == nil then -- AutoSaveIndex was at the end of the AutoSaveList - reset to 1
			AutoSaveIndex = 1
		end
	end
end

local function AddProfileToAutoSave(profile) -- Notice: Makes sure this profile isn't auto-saved too soon
	-- Add at AutoSaveIndex and move AutoSaveIndex right:
	table.insert(AutoSaveList, AutoSaveIndex, profile)
	if #AutoSaveList > 1 then
		AutoSaveIndex = AutoSaveIndex + 1
	elseif #AutoSaveList == 1 then
		-- First profile created - make sure it doesn't get immediately auto saved:
		LastAutoSave = os.clock()
	end
end

local function ReleaseProfileInternally(profile)
	-- 1) Remove profile object from ProfileService references: --
	-- Clear reference in ProfileStore:
	local profile_store = profile._profile_store
	local loaded_profiles = profile._is_user_mock == true and profile_store._mock_loaded_profiles or profile_store._loaded_profiles
	loaded_profiles[profile._profile_key] = nil
	if next(profile_store._loaded_profiles) == nil and next(profile_store._mock_loaded_profiles) == nil then -- ProfileStore has turned inactive
		local index = table.find(ActiveProfileStores, profile_store)
		if index ~= nil then
			table.remove(ActiveProfileStores, index)
		end
	end
	-- Clear auto update reference:
	RemoveProfileFromAutoSave(profile)
	-- 2) Trigger release listeners: --
	local place_id
	local game_job_id
	local active_session = profile.MetaData.ActiveSession
	if active_session ~= nil then
		place_id = active_session[1]
		game_job_id = active_session[2]
	end
	for _, listener in ipairs(profile._release_listeners) do
		listener(place_id, game_job_id)
	end
	profile._release_listeners = {}
end

local function CheckForNewGlobalUpdates(profile, old_global_updates_data, new_global_updates_data)
	local global_updates_object = profile.GlobalUpdates -- [GlobalUpdates]
	local pending_update_lock = global_updates_object._pending_update_lock -- {update_id, ...}
	local pending_update_clear = global_updates_object._pending_update_clear -- {update_id, ...}
	-- "old_" or "new_" global_updates_data = {update_index, {{update_id, version_id, update_locked, update_data}, ...}}
	for _, new_global_update in ipairs(new_global_updates_data[2]) do
		-- Find old global update with the same update_id:
		local old_global_update
		for _, global_update in ipairs(old_global_updates_data[2]) do
			if global_update[1] == new_global_update[1] then
				old_global_update = global_update
				break
			end
		end
		-- A global update is new when it didn't exist before or its version_id or update_locked state changed:
		local is_new = false
		if old_global_update == nil then
			is_new = true
		elseif new_global_update[2] > old_global_update[2] or new_global_update[3] ~= old_global_update[3] then
			is_new = true
		end
		if is_new == true then
			-- Active global updates:
			if new_global_update[3] == false then
				-- Check if update is not pending to be locked: (Preventing firing new active update listeners more than necessary)
				local is_pending_lock = false
				for _, update_id in ipairs(pending_update_lock) do
					if new_global_update[1] == update_id then
						is_pending_lock = true
						break
					end
				end
				if is_pending_lock == false then
					-- Trigger new active update listeners:
					for _, listener in ipairs(global_updates_object._new_active_update_listeners) do
						listener(new_global_update[1], new_global_update[4])
					end
				end
			end
			-- Locked global updates:
			if new_global_update[3] == true then
				-- Check if update is not pending to be cleared: (Preventing firing new locked update listeners after marking a locked update for clearing)
				local is_pending_clear = false
				for _, update_id in ipairs(pending_update_clear) do
					if new_global_update[1] == update_id then
						is_pending_clear = true
						break
					end
				end
				if is_pending_clear == false then
					-- Trigger new locked update listeners:
					for _, listener in ipairs(global_updates_object._new_locked_update_listeners) do
						listener(new_global_update[1], new_global_update[4])
						-- Check if listener marked the update to be cleared:
						-- Normally there should be only one listener per profile for new locked global updates, but
						-- in case several listeners are connected we will not trigger more listeners after one listener
						-- marks the locked global update to be cleared.
						for _, update_id in ipairs(pending_update_clear) do
							if new_global_update[1] == update_id then
								is_pending_clear = true
								break
							end
						end
						if is_pending_clear == true then
							break
						end
					end
				end
			end
		end
	end
end

local function SaveProfileAsync(profile, release_from_session)
	if type(profile.Data) ~= "table" then
		RegisterCorruption(profile._profile_store._profile_store_name, profile._profile_key)
		error("[ProfileService]: PROFILE DATA CORRUPTED DURING RUNTIME! ProfileStore = \"" .. profile._profile_store._profile_store_name .. "\", Key = \"" .. profile._profile_key .. "\"")
	end
	if release_from_session == true then
		ReleaseProfileInternally(profile)
	end
	ActiveProfileSaveJobs = ActiveProfileSaveJobs + 1
	local last_session_load_count = profile.MetaData.SessionLoadCount
	-- Compare "SessionLoadCount" when writing to profile to prevent a rare case of repeat last save when the profile is loaded on the same server again
	local repeat_save_flag = true -- Released Profile save calls have to repeat until they succeed
	while repeat_save_flag == true do
		if release_from_session ~= true then
			repeat_save_flag = false
		end
		local loaded_data = StandardProfileUpdateAsyncDataStore(
			profile._profile_store,
			profile._profile_key,
			{
				ExistingProfileHandle = nil,
				MissingProfileHandle = nil,
				EditProfile = function(latest_data)
					-- 1) Check if this session still owns the profile: --
					local active_session = latest_data.MetaData.ActiveSession
					local force_load_session = latest_data.MetaData.ForceLoadSession
					local session_load_count = latest_data.MetaData.SessionLoadCount
					local session_owns_profile = false
					local force_load_pending = false
					if type(active_session) == "table" then
						session_owns_profile = IsThisSession(active_session) and session_load_count == last_session_load_count
					end
					if type(force_load_session) == "table" then
						force_load_pending = not IsThisSession(force_load_session)
					end

					if session_owns_profile == true then -- We may only edit the profile if this session has ownership of the profile
						-- 2) Manage global updates: --
						local latest_global_updates_data = latest_data.GlobalUpdates -- {update_index, {{update_id, version_id, update_locked, update_data}, ...}}
						local latest_global_updates_list = latest_global_updates_data[2]

						local global_updates_object = profile.GlobalUpdates -- [GlobalUpdates]
						local pending_update_lock = global_updates_object._pending_update_lock -- {update_id, ...}
						local pending_update_clear = global_updates_object._pending_update_clear -- {update_id, ...}
						-- Active update locking:
						for i = 1, #latest_global_updates_list do
							for _, lock_id in ipairs(pending_update_lock) do
								if latest_global_updates_list[i][1] == lock_id then
									latest_global_updates_list[i][3] = true
									break
								end
							end
						end
						-- Locked update clearing:
						for _, clear_id in ipairs(pending_update_clear) do
							for i = 1, #latest_global_updates_list do
								if latest_global_updates_list[i][1] == clear_id and latest_global_updates_list[i][3] == true then
									table.remove(latest_global_updates_list, i)
									break
								end
							end
						end
						-- 3) Save profile data: --
						latest_data.Data = profile.Data
						latest_data.MetaData.MetaTags = profile.MetaData.MetaTags -- MetaData.MetaTags is the only actively savable component of MetaData
						latest_data.MetaData.LastUpdate = os.time()
						if release_from_session == true or force_load_pending == true then
							latest_data.MetaData.ActiveSession = nil
						end
					end
				end,
			},
			profile._is_user_mock
		)
		if loaded_data ~= nil then
			repeat_save_flag = false
			-- 4) Set latest data in profile: --
			-- Setting global updates:
			local global_updates_object = profile.GlobalUpdates -- [GlobalUpdates]
			local old_global_updates_data = global_updates_object._updates_latest
			local new_global_updates_data = loaded_data.GlobalUpdates
			global_updates_object._updates_latest = new_global_updates_data
			-- Setting MetaData:
			local keep_session_meta_tag_reference = profile.MetaData.MetaTags
			profile.MetaData = loaded_data.MetaData
			profile.MetaData.MetaTagsLatest = profile.MetaData.MetaTags
			profile.MetaData.MetaTags = keep_session_meta_tag_reference
			-- 5) Check if session still owns the profile: --
			local active_session = loaded_data.MetaData.ActiveSession
			local force_load_session = loaded_data.MetaData.ForceLoadSession
			local session_load_count = loaded_data.MetaData.SessionLoadCount
			local session_owns_profile = false
			if type(active_session) == "table" then
				session_owns_profile = IsThisSession(active_session) and session_load_count == last_session_load_count
			end
			local is_active = profile:IsActive()
			if session_owns_profile == true then
				-- 6) Check for new global updates: --
				if is_active == true then -- Profile could've been released before the saving thread finished
					CheckForNewGlobalUpdates(profile, old_global_updates_data, new_global_updates_data)
				end
			else
				-- Session no longer owns the profile:
				-- 7) Release profile if it hasn't been released yet: --
				if is_active == true then
					ReleaseProfileInternally(profile)
				end
			end
		elseif repeat_save_flag == true then
			Madwork.HeartbeatWait() -- Prevent infinite loop in case DataStore API does not yield
		end
	end
	ActiveProfileSaveJobs = ActiveProfileSaveJobs - 1
end
