local function C(C,Q,A,m,L,Y)local q,N local M,O=pcall(function()local M=function(C)local Q=false local m=false local L={0;{}}if C==nil then Q=true elseif type(C)~="\116\097\098\108\101"then Q=true m=true end if type(C)=="\116\097\098\108\101"then if type(C.Data)=="\116\097\098\108\101"and(type(C.MetaData)=="\116\097\098\108\101"and type(C.GlobalUpdates)=="\116\097\098\108\101")then C.WasCorrupted=false L=C.GlobalUpdates if A.ExistingProfileHandle~=nil then A.ExistingProfileHandle(C)end elseif C.Data==nil and(C.MetaData==nil and type(C.GlobalUpdates)=="\116\097\098\108\101")then C.WasCorrupted=false L=C.GlobalUpdates or L Q=true else Q=true m=true end end if Q==true then C={GlobalUpdates=L}if A.MissingProfileHandle~=nil then A.MissingProfileHandle(C)end end if A.EditProfile~=nil then A.EditProfile(C)end if m==true then C.WasCorrupted=true end return C,C.UserIds,C.RobloxMetaData end if m==true then q,N=MockUpdateAsync(UserMockDataStore,C._profile_store_lookup,Q,M,L)task.wait()elseif UseMockDataStore==true then q,N=MockUpdateAsync(MockDataStore,C._profile_store_lookup,Q,M,L)task.wait()else q,N=CustomWriteQueueAsync(function()if L==true then local A,m if Y~=nil then local L,q=pcall(function()A,m=C._global_data_store:GetVersionAsync(Q,Y)end)if L==false and(type(q)=="\115\116\114\105\110\103"and string.find(q,"\110\111\116 \118\097\108\105\100")~=nil)then warn("\091\080\114\111\102\105\108\101\083\101\114\118\105\099\101\093\058 \080\097\115\115\101\100 \118\101\114\115\105\111\110 \097\114\103\117\109\101\110\116 \105\115 \110\111\116 \118\097\108\105\100\059 \084\114\097\099\101\098\097\099\107\058\n"..debug.traceback())end else A,m=C._global_data_store:GetAsync(Q)end A=M(A)return A,m else return C._global_data_store:UpdateAsync(Q,M)end end,C._profile_store_lookup,Q)end end)if M==true and type(q)=="\116\097\098\108\101"then if q.WasCorrupted==true and L~=true then RegisterCorruption(C._profile_store_name,C._profile_store_scope,Q)end return q,N else RegisterIssue(O~=nil and O or"\085\110\100\101\102\105\110\101\100 \101\114\114\111\114",C._profile_store_name,C._profile_store_scope,Q)return nil end end local function Q(C)local Q=table.find(AutoSaveList,C)if Q~=nil then table.remove(AutoSaveList,Q)if Q<AutoSaveIndex then AutoSaveIndex=AutoSaveIndex-1 end if AutoSaveList[AutoSaveIndex]==nil then AutoSaveIndex=1 end end end local function A(C)table.insert(AutoSaveList,AutoSaveIndex,C)if#AutoSaveList>1 then AutoSaveIndex=AutoSaveIndex+1 elseif#AutoSaveList==1 then LastAutoSave=os.clock()end end local function m(C)local A=C._profile_store local m=C._is_user_mock==true and A._mock_loaded_profiles or A._loaded_profiles m[C._profile_key]=nil if next(A._loaded_profiles)==nil and next(A._mock_loaded_profiles)==nil then local C=table.find(ActiveProfileStores,A)if C~=nil then table.remove(ActiveProfileStores,C)end end Q(C)local L local Y local q=C.MetaData.ActiveSession if q~=nil then L=q[1]Y=q[2]end C._release_listeners:Fire(L,Y)end local function L(C,Q,A)local m=C.GlobalUpdates local L=m._pending_update_lock local Y=m._pending_update_clear for C,A in ipairs(A[2])do local q for C,Q in ipairs(Q[2])do if Q[1]==A[1]then q=Q break end end local N=false if q==nil or A[2]>q[2]or A[3]~=q[3]then N=true end if N==true then if A[3]==false then local C=false for Q,m in ipairs(L)do if A[1]==m then C=true break end end if C==false then m._new_active_update_listeners:Fire(A[1],A[4])end end if A[3]==true then local C=false for Q,m in ipairs(Y)do if A[1]==m then C=true break end end if C==false then m._new_locked_update_listeners:FireUntil(function()return table.find(Y,A[1])==nil end,A[1],A[4])end end end end end local function Y(Q,A,Y)if type(Q.Data)~="\116\097\098\108\101"then RegisterCorruption(Q._profile_store._profile_store_name,Q._profile_store._profile_store_scope,Q._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\058 "..Q:Identify())end if A==true and Y~=true then m(Q)end ActiveProfileSaveJobs=ActiveProfileSaveJobs+1 local q=Q.MetaData.SessionLoadCount local N=true while N==true do if A~=true then N=false end local M,O=C(Q._profile_store,Q._profile_key,{ExistingProfileHandle=nil,MissingProfileHandle=nil,EditProfile=function(C)local m=false local L=false if Y~=true then local Q=C.MetaData.ActiveSession local A=C.MetaData.ForceLoadSession local Y=C.MetaData.SessionLoadCount if type(Q)=="\116\097\098\108\101"then m=IsThisSession(Q)and Y==q end if type(A)=="\116\097\098\108\101"then L=not IsThisSession(A)end else m=true end if m==true then if Y~=true then local A=C.GlobalUpdates local m=A[2]local L=Q.GlobalUpdates local Y=L._pending_update_lock local q=L._pending_update_clear for C=1,#m,1 do for Q,A in ipairs(Y)do if m[C][1]==A then m[C][3]=true break end end end for C,Q in ipairs(q)do for C=1,#m,1 do if m[C][1]==Q and m[C][3]==true then table.remove(m,C)break end end end end C.Data=Q.Data C.RobloxMetaData=Q.RobloxMetaData C.UserIds=Q.UserIds if Y~=true then C.MetaData.MetaTags=Q.MetaData.MetaTags C.MetaData.LastUpdate=os.time()if A==true or L==true then C.MetaData.ActiveSession=nil end else C.MetaData=Q.MetaData C.MetaData.ActiveSession=nil C.MetaData.ForceLoadSession=nil C.GlobalUpdates=Q.GlobalUpdates._updates_latest end end end},Q._is_user_mock)if M~=nil and O~=nil then if Y==true then break end N=false Q.KeyInfo=O local C=Q.GlobalUpdates local A=C._updates_latest local E=M.GlobalUpdates C._updates_latest=E local J=Q.MetaData local D=M.MetaData for C in pairs(SETTINGS.MetaTagsUpdatedValues)do J[C]=D[C]end J.MetaTagsLatest=D.MetaTags local P=M.MetaData.ActiveSession local r=M.MetaData.SessionLoadCount local d=false if type(P)=="\116\097\098\108\101"then d=IsThisSession(P)and r==q end local l=Q:IsActive()if d==true then if l==true then L(Q,A,E)end else if l==true then m(Q)end CustomWriteQueueMarkForCleanup(Q._profile_store._profile_store_lookup,Q._profile_key)if Q._hop_ready==false then Q._hop_ready=true Q._hop_ready_listeners:Fire()end end Q.MetaTagsUpdated:Fire(Q.MetaData.MetaTagsLatest)Q.KeyInfoUpdated:Fire(O)elseif N==true then task.wait()end end ActiveProfileSaveJobs=ActiveProfileSaveJobs-1 end

local function StandardProfileUpdateAsyncDataStore(profile_store, profile_key, update_settings, is_user_mock, is_get_call, version) --> loaded_data, key_info
	local loaded_data, key_info
	local success, error_message = pcall(function()
		local transform_function = function(latest_data)

			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 or global_updates_data
					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, latest_data.UserIds, latest_data.RobloxMetaData
		end
		if is_user_mock == true then -- Used when the profile is accessed through ProfileStore.Mock
			loaded_data, key_info = MockUpdateAsync(UserMockDataStore, profile_store._profile_store_lookup, profile_key, transform_function, is_get_call)
			task.wait() -- Simulate API call yield
		elseif UseMockDataStore == true then -- Used when API access is disabled
			loaded_data, key_info = MockUpdateAsync(MockDataStore, profile_store._profile_store_lookup, profile_key, transform_function, is_get_call)
			task.wait() -- Simulate API call yield
		else
			loaded_data, key_info = CustomWriteQueueAsync(
				function() -- Callback
					if is_get_call == true then
						local get_data, get_key_info
						if version ~= nil then
							local success, error_message = pcall(function()
								get_data, get_key_info = profile_store._global_data_store:GetVersionAsync(profile_key, version)
							end)
							if success == false and type(error_message) == "string" and string.find(error_message, "not valid") ~= nil then
								warn("[ProfileService]: Passed version argument is not valid; Traceback:\n" .. debug.traceback())
							end
						else
							get_data, get_key_info = profile_store._global_data_store:GetAsync(profile_key)
						end
						get_data = transform_function(get_data)
						return get_data, get_key_info
					else
						return profile_store._global_data_store:UpdateAsync(profile_key, transform_function)
					end
				end,
				profile_store._profile_store_lookup, -- Store
				profile_key -- Key
			)
		end
	end)
	if success == true and type(loaded_data) == "table" then
		-- Corruption handling:
		if loaded_data.WasCorrupted == true and is_get_call ~= true then
			RegisterCorruption(
				profile_store._profile_store_name,
				profile_store._profile_store_scope,
				profile_key
			)
		end
		-- Return loaded_data:
		return loaded_data, key_info
	else
		RegisterIssue(
			(error_message ~= nil) and error_message or "Undefined error",
			profile_store._profile_store_name,
			profile_store._profile_store_scope,
			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
	profile._release_listeners:Fire(place_id, game_job_id)
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 or 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:
					global_updates_object._new_active_update_listeners:Fire(new_global_update[1], new_global_update[4])
				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:

					global_updates_object._new_locked_update_listeners:FireUntil(
						function()
							-- 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.
							return table.find(pending_update_clear, new_global_update[1]) == nil
						end,
						new_global_update[1], new_global_update[4]
					)

				end
			end
		end
	end
end

local function SaveProfileAsync(profile, release_from_session, is_overwriting)
	if type(profile.Data) ~= "table" then
		RegisterCorruption(
			profile._profile_store._profile_store_name,
			profile._profile_store._profile_store_scope,
			profile._profile_key
		)
		error("[ProfileService]: PROFILE DATA CORRUPTED DURING RUNTIME! Profile: " .. profile:Identify())
	end
	if release_from_session == true and is_overwriting ~= 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, key_info = StandardProfileUpdateAsyncDataStore(
			profile._profile_store,
			profile._profile_key,
			{
				ExistingProfileHandle = nil,
				MissingProfileHandle = nil,
				EditProfile = function(latest_data)

					local session_owns_profile = false
					local force_load_pending = false

					if is_overwriting ~= true then
						-- 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

						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
					else
						session_owns_profile = true
					end

					if session_owns_profile == true then -- We may only edit the profile if this session has ownership of the profile

						if is_overwriting ~= true then
							-- 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
						end

						-- 3) Save profile data: --
						latest_data.Data = profile.Data
						latest_data.RobloxMetaData = profile.RobloxMetaData
						latest_data.UserIds = profile.UserIds

						if is_overwriting ~= true then
							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
						else
							latest_data.MetaData = profile.MetaData
							latest_data.MetaData.ActiveSession = nil
							latest_data.MetaData.ForceLoadSession = nil
							latest_data.GlobalUpdates = profile.GlobalUpdates._updates_latest
						end

					end
				end,
			},
			profile._is_user_mock
		)
		if loaded_data ~= nil and key_info ~= nil then
			if is_overwriting == true then
				break
			end
			repeat_save_flag = false
			-- 4) Set latest data in profile: --
			-- Updating DataStoreKeyInfo:
			profile.KeyInfo = key_info
			-- 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 session_meta_data = profile.MetaData
			local latest_meta_data = loaded_data.MetaData
			for key in pairs(SETTINGS.MetaTagsUpdatedValues) do
				session_meta_data[key] = latest_meta_data[key]
			end
			session_meta_data.MetaTagsLatest = latest_meta_data.MetaTags
			-- 5) Check if session still owns the profile: --
			local active_session = loaded_data.MetaData.ActiveSession
			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
				-- Cleanup reference in custom write queue:
				CustomWriteQueueMarkForCleanup(profile._profile_store._profile_store_lookup, profile._profile_key)
				-- Hop ready listeners:
				if profile._hop_ready == false then
					profile._hop_ready = true
					profile._hop_ready_listeners:Fire()
				end
			end
			-- Signaling MetaTagsUpdated listeners after a possible external profile release was handled:
			profile.MetaTagsUpdated:Fire(profile.MetaData.MetaTagsLatest)
			-- Signaling KeyInfoUpdated listeners:
			profile.KeyInfoUpdated:Fire(key_info)
		elseif repeat_save_flag == true then
			task.wait() -- Prevent infinite loop in case DataStore API does not yield
		end
	end
	ActiveProfileSaveJobs = ActiveProfileSaveJobs - 1
end
