function DataStore2.Combine(S,...)for e,z in ipairs({...})do combinedDataStoreInfo[z]=S end end function DataStore2.ClearCache()DataStoreCache={}end function DataStore2.SaveAll(S)if DataStoreCache[S]then for S,e in pairs(DataStoreCache[S])do if e.combinedStore==nil then e:Save()end end end end DataStore2.SaveAllAsync=Promise.promisify(DataStore2.SaveAll)function DataStore2.PatchGlobalSettings(S)for S,e in pairs(S)do assert(Settings[S]~=nil,"\078\111 \115\117\099\104 \107\101\121 \101\120\105\115\116\115\058 "..S)Settings[S]=e end end function DataStore2.__call(S,e,z)assert(typeof(e)=="\115\116\114\105\110\103"and IsPlayer.Check(z),("\068\097\116\097\083\116\111\114\101\050\040\041 \065\080\073 \099\097\108\108 \101\120\112\101\099\116\101\100 \123\115\116\114\105\110\103 \100\097\116\097\083\116\111\114\101\078\097\109\101\044 \080\108\097\121\101\114 \080\108\097\121\101\114\125\044 \103\111\116 \123\037\115\044 \037\115\125"):format(typeof(e),typeof(z)))if DataStoreCache[z]and DataStoreCache[z][e]then return DataStoreCache[z][e]elseif combinedDataStoreInfo[e]then local S=DataStore2(combinedDataStoreInfo[e],z)S:BeforeSave(function(S)for e in pairs(S)do if combinedDataStoreInfo[e]then local l=DataStore2(e,z)local E=l:Get(nil,true)if E~=nil then if l.combinedBeforeSave then E=l.combinedBeforeSave(clone(E))end S[e]=E end end end return S end)local l=setmetatable({combinedName=e,combinedStore=S},{__index=function(e,z)return CombinedDataStore[z]or S[z]end})if not DataStoreCache[z]then DataStoreCache[z]={}end DataStoreCache[z][e]=l return l end local l={Name=e,UserId=z.UserId;callbacks={},beforeInitialGet={};afterSave={},bindToClose={}}l.savingMethod=SavingMethods[Settings.SavingMethod].new(l)setmetatable(l,DataStoreMetatable)local E,s=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116"),false local B=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local f=function()if not s then Promise.defer(function()B:Fire()end)E.Event:Wait()end local S=l:Get(nil,true)for e,l in ipairs(l.bindToClose)do l(z,S)end end local m,y=pcall(function()game:BindToClose(function()if f==nil then return end f()end)end)if not m then warn("\068\097\116\097\083\116\111\114\101\050 \099\111\117\108\100 \110\111\116 \066\105\110\100\084\111\067\108\111\115\101",y)end;(Promise.race({Promise.fromEvent(B.Event),Promise.fromEvent(z.AncestryChanged,function()return not z:IsDescendantOf(game)end)})):andThen(function()(((l:SaveAsync()):andThen(function()print("\080\108\097\121\101\114 "..(z.Name.." \108\101\102\116\044 \115\097\118\101\100"),e,"\058",l.value)end)):catch(function(S)warn("\101\114\114\111\114 \119\104\101\110 \080\108\097\121\101\114 \108\101\102\116\033",S)end)):finally(function()s=true E:Fire()end)return(Promise.delay(40)):andThen(function()DataStoreCache[z]=nil f=nil end)end)if not DataStoreCache[z]then DataStoreCache[z]={}end DataStoreCache[z][e]=l return l end DataStore2.Constants=Constants return setmetatable(DataStore2,DataStore2)

function DataStore2.Combine(mainKey, ...)
	for _, name in ipairs({...}) do
		combinedDataStoreInfo[name] = mainKey
	end
end

function DataStore2.ClearCache()
	DataStoreCache = {}
end

function DataStore2.SaveAll(Player)
	if DataStoreCache[Player] then
		for _, dataStore in pairs(DataStoreCache[Player]) do
			if dataStore.combinedStore == nil then
				dataStore:Save()
			end
		end
	end
end

DataStore2.SaveAllAsync = Promise.promisify(DataStore2.SaveAll)

function DataStore2.PatchGlobalSettings(patch)
	for key, value in pairs(patch) do
		assert(Settings[key] ~= nil, "No such key exists: " .. key)
		-- TODO: Implement type checking with this when osyris' t is in
		Settings[key] = value
	end
end

function DataStore2.__call(_, dataStoreName, Player)
	assert(
		typeof(dataStoreName) == "string" and IsPlayer.Check(Player),
		("DataStore2() API call expected {string dataStoreName, Player Player}, got {%s, %s}")
		:format(
			typeof(dataStoreName),
			typeof(Player)
		)
	)

	if DataStoreCache[Player] and DataStoreCache[Player][dataStoreName] then
		return DataStoreCache[Player][dataStoreName]
	elseif combinedDataStoreInfo[dataStoreName] then
		local dataStore = DataStore2(combinedDataStoreInfo[dataStoreName], Player)

		dataStore:BeforeSave(function(combinedData)
			for key in pairs(combinedData) do
				if combinedDataStoreInfo[key] then
					local combinedStore = DataStore2(key, Player)
					local value = combinedStore:Get(nil, true)
					if value ~= nil then
						if combinedStore.combinedBeforeSave then
							value = combinedStore.combinedBeforeSave(clone(value))
						end
						combinedData[key] = value
					end
				end
			end

			return combinedData
		end)

		local combinedStore = setmetatable({
			combinedName = dataStoreName,
			combinedStore = dataStore,
		}, {
			__index = function(_, key)
				return CombinedDataStore[key] or dataStore[key]
			end,
		})

		if not DataStoreCache[Player] then
			DataStoreCache[Player] = {}
		end

		DataStoreCache[Player][dataStoreName] = combinedStore
		return combinedStore
	end

	local dataStore = {
		Name = dataStoreName,
		UserId = Player.UserId,
		callbacks = {},
		beforeInitialGet = {},
		afterSave = {},
		bindToClose = {},
	}

	dataStore.savingMethod = SavingMethods[Settings.SavingMethod].new(dataStore)

	setmetatable(dataStore, DataStoreMetatable)

	local saveFinishedEvent, isSaveFinished = Instance.new("BindableEvent"), false
	local bindToCloseEvent = Instance.new("BindableEvent")

	local bindToCloseCallback = function()
		if not isSaveFinished then
			-- Defer to avoid a race between connecting and firing "saveFinishedEvent"
			Promise.defer(function()
				bindToCloseEvent:Fire() -- Resolves the Promise.race to save the data
			end)

			saveFinishedEvent.Event:Wait()
		end

		local value = dataStore:Get(nil, true)

		for _, bindToClose in ipairs(dataStore.bindToClose) do
			bindToClose(Player, value)
		end
	end

	local success, errorMessage = pcall(function()
		game:BindToClose(function()
			if bindToCloseCallback == nil then
				return
			end
	
			bindToCloseCallback()
		end)
	end)
	if not success then
		warn("DataStore2 could not BindToClose", errorMessage)
	end

	Promise.race({
		Promise.fromEvent(bindToCloseEvent.Event),
		Promise.fromEvent(Player.AncestryChanged, function()
			return not Player:IsDescendantOf(game)
		end),
	}):andThen(function()
		dataStore:SaveAsync():andThen(function()
			print("Player "..Player.Name.." left, saved", dataStoreName, ":", dataStore["value"])
		end):catch(function(error)
			-- TODO: Something more elegant
			warn("error when Player left!", error)
		end):finally(function()
			isSaveFinished = true
			saveFinishedEvent:Fire()
		end)

		--Give a long delay for people who haven't figured out the cache :^(
		return Promise.delay(40):andThen(function() 
			DataStoreCache[Player] = nil
			bindToCloseCallback = nil
		end)
	end)

	if not DataStoreCache[Player] then
		DataStoreCache[Player] = {}
	end

	DataStoreCache[Player][dataStoreName] = dataStore

	return dataStore
end

DataStore2.Constants = Constants

return setmetatable(DataStore2, DataStore2)
