local k=require(script.Parent.Parent.MigrationLayer)local d=require(script.Parent.Parent.Parent.Error)local N=require(script.Parent.Parent.Parent.Parent.Promise)local y=(require(script.Parent.Parent.Parent.getTime)).getTime local u=game:GetService("\072\116\116\112\083\101\114\118\105\099\101")local h=300 local n=7 local function c()return d.new({kind=d.Kind.LockConsistencyViolation;error="\076\111\099\107 \119\097\115 \099\104\097\110\103\101\100 \097\102\116\101\114 \119\101 \097\099\113\117\105\114\101\100 \105\116\046 \065\098\111\114\116\105\110\103 \111\112\101\114\097\116\105\111\110\046"})end local C={}C.__index=C function C.new(k,d,N)assert(N~=nil,"\109\105\103\114\097\116\105\111\110\115 \105\115 \110\105\108")return setmetatable({collection=k,key=d,_lockId=u:GenerateGUID();_locked=false;_value=nil,_lastWrite=-1,_pendingData=nil,_pendingPromise=nil;_pendingClose=false,_migrations=N},C)end function C.lock(N)if N._locked then error("\065\116\116\101\109\112\116 \116\111 \108\111\099\107 \119\104\101\110 \097\108\114\101\097\100\121 \108\111\099\107\101\100")end local u N._value=k.update(N.collection,N.key,function(k)k=k or{createdAt=os.time(),updatedAt=os.time()}if type(k.lockedAt)=="\110\117\109\098\101\114"and os.time()-k.lockedAt<h then u=false return nil end k.lockId=N._lockId k.lockedAt=os.time()u=true return k end,N._migrations)if u and N._value.lockId==N._lockId then N._locked=true N._lastWrite=y()return N end error(d.new({kind=d.Kind.CouldNotAcquireLock,error="\067\111\117\108\100 \110\111\116 \097\099\113\117\105\114\101 \108\111\099\107"}))end function C._checkConsistency(d,k)return k.lockId and k.lockId==d._lockId end function C._ensureLocked(k)if not k._locked then error("\065\116\116\101\109\112\116 \116\111 \097\099\099\101\115\115 \076\111\099\107\083\101\115\115\105\111\110 \119\105\116\104\111\117\116 \097 \108\111\099\107")end end function C.write(u,d)u:_ensureLocked()if u._pendingClose then error("\065\116\116\101\109\112\116 \116\111 \119\114\105\116\101 \116\111 \097 \076\111\099\107\083\101\115\115\105\111\110 \116\104\097\116 \105\115 \112\101\110\100\105\110\103 \116\111 \098\101 \099\108\111\115\101\100\046")end if d==nil then warn("\091\068\101\098\117\103\044 \114\101\109\111\118\101 \102\111\114 \114\101\108\101\097\115\101\093 \076\111\099\107\083\101\115\115\105\111\110\058\119\114\105\116\101 \100\097\116\097 \105\115 \110\105\108\033")end if y()-u._lastWrite<n then warn("\081\117\101\117\101\105\110\103 \107\101\121 \061",u.collection,u.key)u._pendingData=d if not u._pendingPromise then u._pendingPromise=(N.delay(n-(y()-u._lastWrite))):andThen(function()u._pendingPromise=nil if u._pendingClose then u._pendingClose=false u:unlock()else local k=u._pendingData u._pendingData=nil u:write(k)end end)end return u._pendingPromise:expect()end local h u._value=k.update(u.collection,u.key,function(k)if not u:_checkConsistency(k)then h=c()return nil end k.updatedAt=os.time()k.lockedAt=os.time()k.data=d return k end,u._migrations)if h then error(h)end u._lastWrite=y()end function C.read(k)k:_ensureLocked()return k._value.data end function C.getUpdatedTimestamp(k)k:_ensureLocked()return k._value.updatedAt end function C.getCreatedTimestamp(k)k:_ensureLocked()return k._value.createdAt end function C.unlockWithFinalData(d,k)d._pendingData=k return d:unlock()end function C.unlock(d)if y()-d._lastWrite<n or d._pendingPromise then d._pendingClose=true if d._pendingPromise==nil then d._pendingPromise=(N.delay(n-(y()-d._lastWrite))):andThen(function()d._pendingPromise=nil d:unlock()end)end return d._pendingPromise:expect()end d._locked=false d._value=nil k.update(d.collection,d.key,function(k)if not d:_checkConsistency(k)then return nil end if d._pendingData then k.data=d._pendingData d._pendingData=nil end k.lockedAt=nil k.lockId=nil return k end,d._migrations)end return C
local MigrationLayer = require(script.Parent.Parent.MigrationLayer)
local Error = require(script.Parent.Parent.Parent.Error)
local Promise = require(script.Parent.Parent.Parent.Parent.Promise)
local getTime = require(script.Parent.Parent.Parent.getTime).getTime

local HttpService = game:GetService("HttpService")

local LOCK_EXPIRE = 60 * 5
local WRITE_MAX_INTERVAL = 7

local function consistencyError()
	return Error.new({
		kind = Error.Kind.LockConsistencyViolation,
		error = "Lock was changed after we acquired it. Aborting operation."
	})
end

local LockSession = {}
LockSession.__index = LockSession

function LockSession.new(collection, key, migrations)
	assert(migrations ~= nil, "migrations is nil")

	return setmetatable({
		collection = collection;
		key = key;
		_lockId = HttpService:GenerateGUID();
		_locked = false;
		_value = nil;
		_lastWrite = -1;
		_pendingData = nil;
		_pendingPromise = nil;
		_pendingClose = false;
		_migrations = migrations;
	}, LockSession)
end

function LockSession:lock()
	if self._locked then
		error("Attempt to lock when already locked")
	end

	local success

	self._value = MigrationLayer.update(self.collection, self.key, function(value)
		value = value or {
			createdAt = os.time();
			updatedAt = os.time();
		}

		if type(value.lockedAt) == "number" and os.time() - value.lockedAt < LOCK_EXPIRE then
			success = false
			return nil
		end

		value.lockId = self._lockId
		value.lockedAt = os.time()

		success = true

		return value
	end, self._migrations)

	if success and self._value.lockId == self._lockId then
		self._locked = true
		self._lastWrite = getTime()
		return self
	end

	error(Error.new({
		kind = Error.Kind.CouldNotAcquireLock,
		error = "Could not acquire lock"
	}))
end

function LockSession:_checkConsistency(value)
	return value.lockId and value.lockId == self._lockId
end

function LockSession:_ensureLocked()
	if not self._locked then
		error("Attempt to access LockSession without a lock")
	end
end

function LockSession:write(data)
	self:_ensureLocked()

	if self._pendingClose then
		error("Attempt to write to a LockSession that is pending to be closed.")
	end

	if data == nil then
		warn("[Debug, remove for release] LockSession:write data is nil!")
	end


	if getTime() - self._lastWrite < WRITE_MAX_INTERVAL then
		warn("Queueing key =", self.collection, self.key)
		self._pendingData = data

		if not self._pendingPromise then
			self._pendingPromise = Promise.delay(WRITE_MAX_INTERVAL - (getTime() - self._lastWrite)):andThen(function()
				self._pendingPromise = nil

				if self._pendingClose then
					self._pendingClose = false
					self:unlock()
				else
					local pendingData = self._pendingData
					self._pendingData = nil
					self:write(pendingData)
				end
			end)
		end

		return self._pendingPromise:expect()
	end

	local errorVal
	self._value = MigrationLayer.update(self.collection, self.key, function(value)
		if not self:_checkConsistency(value) then
			errorVal = consistencyError()
			return nil
		end

		value.updatedAt = os.time()
		value.lockedAt = os.time()
		value.data = data

		return value
	end, self._migrations)

	if errorVal then
		error(errorVal)
	end

	self._lastWrite = getTime()
end

function LockSession:read()
	self:_ensureLocked()

	return self._value.data
end

function LockSession:getUpdatedTimestamp()
	self:_ensureLocked()
	return self._value.updatedAt
end

function LockSession:getCreatedTimestamp()
	self:_ensureLocked()
	return self._value.createdAt
end

function LockSession:unlockWithFinalData(data)
	self._pendingData = data

	return self:unlock()
end

function LockSession:unlock()
	if getTime() - self._lastWrite < WRITE_MAX_INTERVAL or self._pendingPromise then
		self._pendingClose = true

		if self._pendingPromise == nil then
			self._pendingPromise = Promise.delay(WRITE_MAX_INTERVAL - (getTime() - self._lastWrite)):andThen(function()
				self._pendingPromise = nil
				self:unlock()
			end)
		end

		return self._pendingPromise:expect()
	end

	self._locked = false
	self._value = nil

	MigrationLayer.update(self.collection, self.key, function(value)
		if not self:_checkConsistency(value) then
			return nil
		end

		if self._pendingData then
			value.data = self._pendingData
			self._pendingData = nil
		end

		value.lockedAt = nil
		value.lockId = nil

		return value
	end, self._migrations)
end

return LockSession