local function D(D,f,s)return string.format("\091\083\116\111\114\101\058\"\037\115\"\059\037\115\075\101\121\058\"\037\115\"\093",D,f~=nil and string.format("\083\099\111\112\101\058\"\037\115\"\059",f)or"",s)end local function f(D,f)if CustomWriteQueue[D]~=nil then CustomWriteQueue[D][f]=nil if next(CustomWriteQueue[D])==nil then CustomWriteQueue[D]=nil end end end local function s(D,s)if CustomWriteQueue[D]~=nil then if CustomWriteQueue[D][s]~=nil then local T=CustomWriteQueue[D][s]local l=T.Queue if T.CleanupJob==nil then T.CleanupJob=RunService.Heartbeat:Connect(function()if os.clock()-T.LastWrite>SETTINGS.RobloxWriteCooldown and#l==0 then T.CleanupJob:Disconnect()f(D,s)end end)end elseif next(CustomWriteQueue[D])==nil then CustomWriteQueue[D]=nil end end end local function T(D,f,s)if CustomWriteQueue[f]==nil then CustomWriteQueue[f]={}end if CustomWriteQueue[f][s]==nil then CustomWriteQueue[f][s]={LastWrite=0;Queue={};CleanupJob=nil}end local T=CustomWriteQueue[f][s]local l=T.Queue if T.CleanupJob~=nil then T.CleanupJob:Disconnect()T.CleanupJob=nil end if os.clock()-T.LastWrite>SETTINGS.RobloxWriteCooldown and#l==0 then T.LastWrite=os.clock()return D()else table.insert(l,D)while true do if os.clock()-T.LastWrite>SETTINGS.RobloxWriteCooldown and l[1]==D then table.remove(l,1)T.LastWrite=os.clock()return D()end task.wait()end end end local function l(D,f)local s=CustomWriteQueue[D]if s~=nil then s=s[f]return s==nil or#s.Queue==0 end return true end local function B()while IsLiveCheckActive==true do task.wait()end end local function X(D)while D._is_pending==true do task.wait()end end local function e(f,s,T,l)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 "..(D(s,T,l)..(" - \""..(tostring(f).."\""))))table.insert(IssueQueue,os.clock())ProfileService.IssueSignal:Fire(tostring(f),s,l)end local function C(f,s,T)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 "..D(f,s,T))ProfileService.CorruptionSignal:Fire(f,T)end local function n(D)local f=tostring(D.VersionId or 0)local s=D.MetaData or{}local T=D.UserIds or{}return{CreatedTime=D.CreatedTime;UpdatedTime=D.UpdatedTime;Version=string.rep("\048",16)..("\046"..(string.rep("\048",10-string.len(f))..(f..("\046"..(string.rep("\048",16).."\046\048\049")))));GetMetadata=function()return DeepCopyTable(s)end;GetUserIds=function()return DeepCopyTable(T)end}end local function z(D,f,s,T,l)local B=D[f]if B==nil then B={}D[f]=B end local X=math.floor(os.time()*1000)local e=B[s]local C=false if e==nil then C=true if l~=true then e={Data=nil,CreatedTime=X;UpdatedTime=X,VersionId=0;UserIds={};MetaData={}}B[s]=e end end local z=C==false and n(e)or nil local d,O,y=T(e and e.Data,z)if d==nil then return nil else if e~=nil and l~=true then e.Data=d e.UserIds=DeepCopyTable(O or{})e.MetaData=DeepCopyTable(y or{})e.VersionId+=1 e.UpdatedTime=X end return DeepCopyTable(d),e~=nil and n(e)or nil end end local function d(D)return D[1]==PlaceId and D[2]==JobId end


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
