local function J(J,p,w)return string.format("\091\083\116\111\114\101\058\"\037\115\"\059\037\115\075\101\121\058\"\037\115\"\093",J,p~=nil and string.format("\083\099\111\112\101\058\"\037\115\"\059",p)or"",w)end local function p(J,p)if CustomWriteQueue[J]~=nil then CustomWriteQueue[J][p]=nil if next(CustomWriteQueue[J])==nil then CustomWriteQueue[J]=nil end end end local function w(J,w)if CustomWriteQueue[J]~=nil then if CustomWriteQueue[J][w]~=nil then local P=CustomWriteQueue[J][w]local W=P.Queue if P.CleanupJob==nil then P.CleanupJob=RunService.Heartbeat:Connect(function()if os.clock()-P.LastWrite>SETTINGS.RobloxWriteCooldown and#W==0 then P.CleanupJob:Disconnect()p(J,w)end end)end elseif next(CustomWriteQueue[J])==nil then CustomWriteQueue[J]=nil end end end local function P(J,p,w)if CustomWriteQueue[p]==nil then CustomWriteQueue[p]={}end if CustomWriteQueue[p][w]==nil then CustomWriteQueue[p][w]={LastWrite=0;Queue={},CleanupJob=nil}end local P=CustomWriteQueue[p][w]local W=P.Queue if P.CleanupJob~=nil then P.CleanupJob:Disconnect()P.CleanupJob=nil end if os.clock()-P.LastWrite>SETTINGS.RobloxWriteCooldown and#W==0 then P.LastWrite=os.clock()return J()else table.insert(W,J)while true do if os.clock()-P.LastWrite>SETTINGS.RobloxWriteCooldown and W[1]==J then table.remove(W,1)P.LastWrite=os.clock()return J()end task.wait()end end end local function W(J,p)local w=CustomWriteQueue[J]if w~=nil then w=w[p]return w==nil or#w.Queue==0 end return true end local function B()while IsLiveCheckActive==true do task.wait()end end local function T(J)while J._is_pending==true do task.wait()end end local function L(p,w,P,W)warn("\091\080\114\111\102\105\108\101\083\101\114\118\105\099\101\093\058 \068\097\116\097\083\116\111\114\101 \065\080\073 \101\114\114\111\114 "..(J(w,P,W)..(" - \""..(tostring(p).."\""))))table.insert(IssueQueue,os.clock())ProfileService.IssueSignal:Fire(tostring(p),w,W)end local function t(p,w,P)warn("\091\080\114\111\102\105\108\101\083\101\114\118\105\099\101\093\058 \082\101\115\111\108\118\101\100 \112\114\111\102\105\108\101 \099\111\114\114\117\112\116\105\111\110 "..J(p,w,P))ProfileService.CorruptionSignal:Fire(p,P)end local function n(J)local p=tostring(J.VersionId or 0)local w=J.MetaData or{}local P=J.UserIds or{}return{CreatedTime=J.CreatedTime;UpdatedTime=J.UpdatedTime,Version=string.rep("\048",16)..("\046"..(string.rep("\048",10-string.len(p))..(p..("\046"..(string.rep("\048",16).."\046\048\049")))));GetMetadata=function()return DeepCopyTable(w)end,GetUserIds=function()return DeepCopyTable(P)end}end local function m(J,p,w,P,W)local B=J[p]if B==nil then B={}J[p]=B end local T=math.floor(os.time()*1000)local L=B[w]local t=false if L==nil then t=true if W~=true then L={Data=nil;CreatedTime=T,UpdatedTime=T;VersionId=0;UserIds={};MetaData={}}B[w]=L end end local m=t==false and n(L)or nil local I,x,i=P(L and L.Data,m)if I==nil then return nil else if L~=nil and W~=true then L.Data=I L.UserIds=DeepCopyTable(x or{})L.MetaData=DeepCopyTable(i or{})L.VersionId+=1 L.UpdatedTime=T end return DeepCopyTable(I),L~=nil and n(L)or nil end end local function I(J)return J[1]==PlaceId and J[2]==JobId end
----- Private functions -----

local function IdentifyProfile(store_name, store_scope, key)
	return string.format(
		'[Store:"%s";%sKey:"%s"]',
		store_name,
		store_scope ~= nil and string.format('Scope:"%s";', store_scope) or "",
		key
	)
end

local function CustomWriteQueueCleanup(store, key)
	if CustomWriteQueue[store] ~= nil then
		CustomWriteQueue[store][key] = nil
		if next(CustomWriteQueue[store]) == nil then
			CustomWriteQueue[store] = nil
		end
	end
end

local function CustomWriteQueueMarkForCleanup(store, key)
	if CustomWriteQueue[store] ~= nil then
		if CustomWriteQueue[store][key] ~= nil then
			local queue_data = CustomWriteQueue[store][key]
			local queue = queue_data.Queue

			if queue_data.CleanupJob == nil then
				queue_data.CleanupJob = RunService.Heartbeat:Connect(function()
					if os.clock() - queue_data.LastWrite > SETTINGS.RobloxWriteCooldown and #queue == 0 then
						queue_data.CleanupJob:Disconnect()
						CustomWriteQueueCleanup(store, key)
					end
				end)
			end
		elseif next(CustomWriteQueue[store]) == nil then
			CustomWriteQueue[store] = nil
		end
	end
end

local function CustomWriteQueueAsync(callback, store, key) --> ... -- Passed return from callback
	if CustomWriteQueue[store] == nil then
		CustomWriteQueue[store] = {}
	end
	if CustomWriteQueue[store][key] == nil then
		CustomWriteQueue[store][key] = { LastWrite = 0, Queue = {}, CleanupJob = nil }
	end

	local queue_data = CustomWriteQueue[store][key]
	local queue = queue_data.Queue

	-- Cleanup job:

	if queue_data.CleanupJob ~= nil then
		queue_data.CleanupJob:Disconnect()
		queue_data.CleanupJob = nil
	end

	-- Queue logic:

	if os.clock() - queue_data.LastWrite > SETTINGS.RobloxWriteCooldown and #queue == 0 then
		queue_data.LastWrite = os.clock()
		return callback()
	else
		table.insert(queue, callback)
		while true do
			if os.clock() - queue_data.LastWrite > SETTINGS.RobloxWriteCooldown and queue[1] == callback then
				table.remove(queue, 1)
				queue_data.LastWrite = os.clock()
				return callback()
			end
			task.wait()
		end
	end
end

local function IsCustomWriteQueueEmptyFor(store, key) --> is_empty [bool]
	local lookup = CustomWriteQueue[store]
	if lookup ~= nil then
		lookup = lookup[key]
		return lookup == nil or #lookup.Queue == 0
	end
	return true
end

local function WaitForLiveAccessCheck() -- This function was created to prevent the ProfileService module yielding execution when required
	while IsLiveCheckActive == true do
		task.wait()
	end
end

local function WaitForPendingProfileStore(profile_store)
	while profile_store._is_pending == true do
		task.wait()
	end
end

local function RegisterIssue(error_message, store_name, store_scope, profile_key) -- Called when a DataStore API call errors
	warn(
		"[ProfileService]: DataStore API error "
			.. IdentifyProfile(store_name, store_scope, profile_key)
			.. ' - "'
			.. tostring(error_message)
			.. '"'
	)
	table.insert(IssueQueue, os.clock()) -- Adding issue time to queue
	ProfileService.IssueSignal:Fire(tostring(error_message), store_name, profile_key)
end

local function RegisterCorruption(store_name, store_scope, profile_key) -- Called when a corrupted profile is loaded
	warn("[ProfileService]: Resolved profile corruption " .. IdentifyProfile(store_name, store_scope, profile_key))
	ProfileService.CorruptionSignal:Fire(store_name, profile_key)
end

local function NewMockDataStoreKeyInfo(params)
	local version_id_string = tostring(params.VersionId or 0)
	local meta_data = params.MetaData or {}
	local user_ids = params.UserIds or {}

	return {
		CreatedTime = params.CreatedTime,
		UpdatedTime = params.UpdatedTime,
		Version = string.rep("0", 16)
			.. "."
			.. string.rep("0", 10 - string.len(version_id_string))
			.. version_id_string
			.. "."
			.. string.rep("0", 16)
			.. "."
			.. "01",

		GetMetadata = function()
			return DeepCopyTable(meta_data)
		end,

		GetUserIds = function()
			return DeepCopyTable(user_ids)
		end,
	}
end

local function MockUpdateAsync(mock_data_store, profile_store_name, key, transform_function, is_get_call) --> loaded_data, key_info
	local profile_store = mock_data_store[profile_store_name]

	if profile_store == nil then
		profile_store = {}
		mock_data_store[profile_store_name] = profile_store
	end

	local epoch_time = math.floor(os.time() * 1000)
	local mock_entry = profile_store[key]
	local mock_entry_was_nil = false

	if mock_entry == nil then
		mock_entry_was_nil = true
		if is_get_call ~= true then
			mock_entry = {
				Data = nil,
				CreatedTime = epoch_time,
				UpdatedTime = epoch_time,
				VersionId = 0,
				UserIds = {},
				MetaData = {},
			}
			profile_store[key] = mock_entry
		end
	end

	local mock_key_info = mock_entry_was_nil == false and NewMockDataStoreKeyInfo(mock_entry) or nil

	local transform, user_ids, roblox_meta_data = transform_function(mock_entry and mock_entry.Data, mock_key_info)

	if transform == nil then
		return nil
	else
		if mock_entry ~= nil and is_get_call ~= true then
			mock_entry.Data = transform
			mock_entry.UserIds = DeepCopyTable(user_ids or {})
			mock_entry.MetaData = DeepCopyTable(roblox_meta_data or {})
			mock_entry.VersionId += 1
			mock_entry.UpdatedTime = epoch_time
		end

		return DeepCopyTable(transform), mock_entry ~= nil and NewMockDataStoreKeyInfo(mock_entry) or nil
	end
end

local function IsThisSession(session_tag)
	return session_tag[1] == PlaceId and session_tag[2] == JobId
end
