local function s(...)return...,{select(2,...)}end local function Z(s)local Z=getmetatable(s)return Z and Z._IsAttempt or false end local function e(s,...)local Z={}Z.Id=tostring(Z)setmetatable(Z,Attempt)return Z:Execute(s,{...})end function Attempt.Execute(W,Z,e)local X,P=s(pcall(Z,unpack(e)))W.Function=Z W.Arguments=e W.Success=X W.Results=P if not X then W.Stack=debug.traceback()W.Skips={}end return W end function Attempt.Then(e,s)local W=e.Results[1]if e.Success and Z(W)then e=W end if not e.Success then e.Skips[#e.Skips+1]=s return e end e:Execute(s,e.Results)return e end function Attempt.Catch(s,...)local W=s.Results[1]if s.Success and Z(W)then s=W end if s.Success or s.Handled then return s end local X={...}local P=#X-1 local c=X[P+1]s.Skips[#s.Skips+1]=X local b=s.Results[1]local n=false if P==0 then n=true elseif type(b)=="\115\116\114\105\110\103"then for s=1,P,1 do if b:match(X[s])then n=true break end end end if n then return(e(c,b,s.Stack,s)):Then(function()s.Handled=true return s end)end return s end function Attempt.Retry(s)if s.Success then return end local Z=s.Skips s.Handled=nil s.Skips=nil s.Stack=nil s.RetryCount=s.RetryCount+1 s:Execute(s.Function,s.Arguments)if s.Success then s.RetryCount=nil end for e=1,#Z,1 do local W=Z[e]local X=getmetatable(W)if type(W)=="\102\117\110\099\116\105\111\110"or X and X.__call then s=s:Then(W)else s=s:Catch(unpack(W))end end return s end return e
-- Result-packaging helper function
local function PackageProtectedCall(...)
	return ..., { select(2, ...) }
end

local function IsAttempt(Object)
	-- Returns whether given object is an attempt

	-- Get object metatable
	local ObjectMetatable = getmetatable(Object)

	-- Return whether metatable indicates object is an attempt
	return ObjectMetatable and ObjectMetatable._IsAttempt or false
end

local function Try(Function, ...)
	-- Creates, starts, and returns a new attempt

	-- Initialize new attempt
	local self = {}
	self.Id = tostring(self)
	setmetatable(self, Attempt)

	-- Run and return attempt for chaining
	return self:Execute(Function, { ... })
end

function Attempt:Execute(Function, Arguments)
	-- Executes function with given arguments, saves results in attempt

	-- Capture function execution results
	local Success, Results = PackageProtectedCall(pcall(Function, unpack(Arguments)))

	-- Update attempt state with execution information
	self.Function = Function
	self.Arguments = Arguments
	self.Success = Success
	self.Results = Results

	-- Get stack trace and start list of skipped operations on failure
	if not Success then
		self.Stack = debug.traceback()
		self.Skips = {}
	end

	-- Return attempt for chaining
	return self
end

function Attempt:Then(Callback)
	-- Passes attempt results to callback, and returns attempt for chaining

	-- Enter new attempt context if received
	local FirstArgument = self.Results[1]
	if self.Success and IsAttempt(FirstArgument) then
		self = FirstArgument
	end

	-- Skip callback if attempt failed
	if not self.Success then
		self.Skips[#self.Skips + 1] = Callback
		return self
	end

	-- Execute callback with results of last attempt
	self:Execute(Callback, self.Results)

	-- Return attempt for chaining
	return self
end

function Attempt:Catch(...)
	-- Passes errors in failed attempt to given callback, returns attempt for chaining

	-- Enter new attempt context if received
	local FirstArgument = self.Results[1]
	if self.Success and IsAttempt(FirstArgument) then
		self = FirstArgument
	end

	-- Skip catching if attempt succeeded
	if self.Success or self.Handled then
		return self
	end

	-- Get arguments
	local Arguments = { ... }

	-- Get predicate count and callback
	local PredicateCount = #Arguments - 1
	local Callback = Arguments[PredicateCount + 1]

	-- Track catching operation for future retry attempts
	self.Skips[#self.Skips + 1] = Arguments

	-- Get attempt error
	local Error = self.Results[1]
	local HandleError = false

	-- Handle any error if no predicates specified
	if PredicateCount == 0 then
		HandleError = true

	-- Handle matching error if predicates specified
	elseif type(Error) == "string" then
		for PredicateId = 1, PredicateCount do
			if Error:match(Arguments[PredicateId]) then
				HandleError = true
				break
			end
		end
	end

	-- Attempt passing error to callback, and return attempt on success
	if HandleError then
		return Try(Callback, Error, self.Stack, self):Then(function()
			self.Handled = true
			return self
		end)
	end

	-- Return attempt for chaining
	return self
end

function Attempt:Retry()
	-- Retries attempt from first failure, applies skipped operations, and returns resulting attempt

	-- Skip retrying if attempt succeeded
	if self.Success then
		return
	end

	-- Get skips after attempt failure
	local Skips = self.Skips

	-- Reset attempt for reexecution
	self.Handled = nil
	self.Skips = nil
	self.Stack = nil

	-- Increment retry counter
	self.RetryCount = self.RetryCount + 1

	-- Retry attempt
	self:Execute(self.Function, self.Arguments)

	-- Reset retry counter if retry succeded
	if self.Success then
		self.RetryCount = nil
	end

	-- Apply skipped operations
	for SkipIndex = 1, #Skips do
		local Skip = Skips[SkipIndex]
		local SkipMetatable = getmetatable(Skip)

		-- Apply callables as `then` operations
		if type(Skip) == "function" or (SkipMetatable and SkipMetatable.__call) then
			self = self:Then(Skip)

		-- Apply non-callables as `catch` operations
		else
			self = self:Catch(unpack(Skip))
		end
	end

	-- Return attempt for chaining
	return self
end

return Try
