local function C(C,l,x,p)local j local v=false local b,I=pcall(function()if x.WipeProfile~=true then local v=function(C)if C=="\080\082\079\070\073\076\069\095\087\073\080\069\068"then C=nil end local l=false local p=false local j={0,{}}if C==nil then l=true elseif type(C)~="\116\097\098\108\101"then l=true p=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 j=C.GlobalUpdates if x.ExistingProfileHandle~=nil then x.ExistingProfileHandle(C)end elseif C.Data==nil and(C.MetaData==nil and type(C.GlobalUpdates)=="\116\097\098\108\101")then C.WasCorrupted=false j=C.GlobalUpdates l=true else l=true p=true end end if l==true then C={GlobalUpdates=j}if x.MissingProfileHandle~=nil then x.MissingProfileHandle(C)end end if x.EditProfile~=nil then x.EditProfile(C)end if p==true then C.WasCorrupted=true end return C end if p==true then j=MockUpdateAsync(UserMockDataStore,C._profile_store_lookup,l,v)Madwork.HeartbeatWait()elseif UseMockDataStore==true then j=MockUpdateAsync(MockDataStore,C._profile_store_lookup,l,v)Madwork.HeartbeatWait()else j=CustomWriteQueueAsync(function()return C._global_data_store:UpdateAsync(l,v)end,C._profile_store_lookup,l)end else if p==true then local x=UserMockDataStore[C._profile_store_lookup]if x~=nil then x[l]=nil end v=true Madwork.HeartbeatWait()elseif UseMockDataStore==true then local x=MockDataStore[C._profile_store_lookup]if x~=nil then x[l]=nil end v=true Madwork.HeartbeatWait()else j=C._global_data_store:UpdateAsync(l,function()return"\080\082\079\070\073\076\069\095\087\073\080\069\068"end)if j=="\080\082\079\070\073\076\069\095\087\073\080\069\068"then v=true end end end end)if x.WipeProfile==true then return v elseif b==true and type(j)=="\116\097\098\108\101"then if j.WasCorrupted==true then RegisterCorruption(C._profile_store_name,C._profile_store_scope,l)end return j else RegisterIssue(I~=nil and I or"\085\110\100\101\102\105\110\101\100 \101\114\114\111\114",C._profile_store_name,C._profile_store_scope,l)return nil end end local function l(C)local l=table.find(AutoSaveList,C)if l~=nil then table.remove(AutoSaveList,l)if l<AutoSaveIndex then AutoSaveIndex=AutoSaveIndex-1 end if AutoSaveList[AutoSaveIndex]==nil then AutoSaveIndex=1 end end end local function x(C)table.insert(AutoSaveList,AutoSaveIndex,C)if#AutoSaveList>1 then AutoSaveIndex=AutoSaveIndex+1 elseif#AutoSaveList==1 then LastAutoSave=os.clock()end end local function p(C)local x=C._profile_store local p=C._is_user_mock==true and x._mock_loaded_profiles or x._loaded_profiles p[C._profile_key]=nil if next(x._loaded_profiles)==nil and next(x._mock_loaded_profiles)==nil then local C=table.find(ActiveProfileStores,x)if C~=nil then table.remove(ActiveProfileStores,C)end end l(C)local j local v local b=C.MetaData.ActiveSession if b~=nil then j=b[1]v=b[2]end C._release_listeners:Fire(j,v)end local function j(C,l,x)local p=C.GlobalUpdates local j=p._pending_update_lock local v=p._pending_update_clear for C,x in ipairs(x[2])do local b for C,l in ipairs(l[2])do if l[1]==x[1]then b=l break end end local I=false if b==nil or x[2]>b[2]or x[3]~=b[3]then I=true end if I==true then if x[3]==false then local C=false for l,p in ipairs(j)do if x[1]==p then C=true break end end if C==false then p._new_active_update_listeners:Fire(x[1],x[4])end end if x[3]==true then local C=false for l,p in ipairs(v)do if x[1]==p then C=true break end end if C==false then p._new_locked_update_listeners:FireUntil(function()return table.find(v,x[1])==nil end,x[1],x[4])end end end end end local function v(l,x)if type(l.Data)~="\116\097\098\108\101"then RegisterCorruption(l._profile_store._profile_store_name,l._profile_store._profile_store_scope,l._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 "..l:Identify())end if x==true then p(l)end ActiveProfileSaveJobs=ActiveProfileSaveJobs+1 local v=l.MetaData.SessionLoadCount local b=true while b==true do if x~=true then b=false end local I=C(l._profile_store,l._profile_key,{ExistingProfileHandle=nil;MissingProfileHandle=nil;EditProfile=function(C)local p=C.MetaData.ActiveSession local j=C.MetaData.ForceLoadSession local b=C.MetaData.SessionLoadCount local I=false local t=false if type(p)=="\116\097\098\108\101"then I=IsThisSession(p)and b==v end if type(j)=="\116\097\098\108\101"then t=not IsThisSession(j)end if I==true then local p=C.GlobalUpdates local j=p[2]local v=l.GlobalUpdates local b=v._pending_update_lock local I=v._pending_update_clear for C=1,#j,1 do for l,x in ipairs(b)do if j[C][1]==x then j[C][3]=true break end end end for C,l in ipairs(I)do for C=1,#j,1 do if j[C][1]==l and j[C][3]==true then table.remove(j,C)break end end end C.Data=l.Data C.MetaData.MetaTags=l.MetaData.MetaTags C.MetaData.LastUpdate=os.time()if x==true or t==true then C.MetaData.ActiveSession=nil end end end},l._is_user_mock)if I~=nil then b=false local C=l.GlobalUpdates local x=C._updates_latest local t=I.GlobalUpdates C._updates_latest=t local U=l.MetaData local d=I.MetaData for C in pairs(SETTINGS.MetaTagsUpdatedValues)do U[C]=d[C]end U.MetaTagsLatest=d.MetaTags local G=I.MetaData.ActiveSession local D=I.MetaData.SessionLoadCount local k=false if type(G)=="\116\097\098\108\101"then k=IsThisSession(G)and D==v end local g=l:IsActive()if k==true then if g==true then j(l,x,t)end else if g==true then p(l)end CustomWriteQueueMarkForCleanup(l._profile_store._profile_store_lookup,l._profile_key)if l._hop_ready==false then l._hop_ready=true l._hop_ready_listeners:Fire()end end l.MetaTagsUpdated:Fire(l.MetaData.MetaTagsLatest)elseif b==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_lookup, 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_lookup, profile_key, transform_function)
				Madwork.HeartbeatWait() -- Simulate API call yield
			else
				loaded_data = CustomWriteQueueAsync(
					function() -- Callback
						return profile_store._global_data_store:UpdateAsync(profile_key, transform_function)
					end,
					profile_store._profile_store_lookup, -- Store
					profile_key -- Key
				)
			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_lookup]
				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_lookup]
				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_store._profile_store_scope,
				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_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)
	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 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 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)
		elseif repeat_save_flag == true then
			Madwork.HeartbeatWait() -- Prevent infinite loop in case DataStore API does not yield
		end
	end
	ActiveProfileSaveJobs = ActiveProfileSaveJobs - 1
end
