local k=game:GetService("\082\117\110\083\101\114\118\105\099\101")local i=game:GetService("\084\101\108\101\112\111\114\116\083\101\114\118\105\099\101")local F=game:GetService("\080\108\097\121\101\114\115")local l=require(script.Parent.Util)local Y=require(script.Parent.Command)local P="\067\109\100\114\067\111\109\109\097\110\100\072\105\115\116\111\114\121"local z=false local R={Cmdr=nil;Registry=nil}function R.Evaluate(X,i,P,z,R)if k:IsClient()==true and P~=F.LocalPlayer then error("\067\097\110\'\116 \101\118\097\108\117\097\116\101 \097 \099\111\109\109\097\110\100 \116\104\097\116 \105\115\110\'\116 \115\101\110\116 \098\121 \116\104\101 \108\111\099\097\108 \112\108\097\121\101\114\046")end local g=l.SplitString(i)local I=table.remove(g,1)local s=X.Registry:GetCommand(I)if s then g=l.MashExcessArguments(g,#s.Args)local k=Y.new({Dispatcher=X,Text=i,CommandObject=s;Alias=I;Executor=P,Arguments=g;Data=R})local F,E=k:Parse(z)if F then return k else return false,E end else return false,("\037\113 \105\115 \110\111\116 \097 \118\097\108\105\100 \099\111\109\109\097\110\100 \110\097\109\101\046 \085\115\101 \116\104\101 \104\101\108\112 \099\111\109\109\097\110\100 \116\111 \115\101\101 \097\108\108 \097\118\097\105\108\097\098\108\101 \099\111\109\109\097\110\100\115\046"):format(tostring(I))end end function R.EvaluateAndRun(P,i,l,Y)l=l or F.LocalPlayer Y=Y or{}if k:IsClient()and Y.IsHuman then P:PushHistory(i)end local z,R=P:Evaluate(i,l,nil,Y.Data)if not z then return R end local X,g=xpcall(function()local k,i=z:Validate(true)if not k then return i end return z:Run()or"\067\111\109\109\097\110\100 \101\120\101\099\117\116\101\100\046"end,function(k)return debug.traceback(tostring(k))end)if not X then warn(("\069\114\114\111\114 \111\099\099\117\114\114\101\100 \119\104\105\108\101 \101\118\097\108\117\097\116\105\110\103 \099\111\109\109\097\110\100 \115\116\114\105\110\103 \037\113\n\037\115"):format(i,tostring(g)))end return X and g or"\065\110 \101\114\114\111\114 \111\099\099\117\114\114\101\100 \119\104\105\108\101 \114\117\110\110\105\110\103 \116\104\105\115 \099\111\109\109\097\110\100\046 \067\104\101\099\107 \116\104\101 \099\111\110\115\111\108\101 \102\111\114 \109\111\114\101 \105\110\102\111\114\109\097\116\105\111\110\046"end function R.Send(l,i,F)if k:IsClient()==false then error("\068\105\115\112\097\116\099\104\101\114\058\083\101\110\100 \099\097\110 \111\110\108\121 \098\101 \099\097\108\108\101\100 \102\114\111\109 \116\104\101 \099\108\105\101\110\116\046")end return l.Cmdr.RemoteFunction:InvokeServer(i,{Data=F})end function R.Run(k,...)if not F.LocalPlayer then error("\068\105\115\112\097\116\099\104\101\114\058\082\117\110 \099\097\110 \111\110\108\121 \098\101 \099\097\108\108\101\100 \102\114\111\109 \116\104\101 \099\108\105\101\110\116\046")end local i={...}local l=i[1]for k=2,#i,1 do l=l..(" "..tostring(i[k]))end local Y,P=k:Evaluate(l,F.LocalPlayer)if not Y then error(P)end local z,R=Y:Validate(true)if not z then error(R)end return Y:Run()end function R.RunHooks(l,i,F,...)if not l.Registry.Hooks[i]then error(("\073\110\118\097\108\105\100 \104\111\111\107 \110\097\109\101\058 \037\113"):format(i),2)end if i=="\066\101\102\111\114\101\082\117\110"and(#l.Registry.Hooks[i]==0 and(F.Group~="\068\101\102\097\117\108\116\085\116\105\108"and(F.Group~="\085\115\101\114\065\108\105\097\115"and F:HasImplementation())))then if k:IsStudio()then if z==false then F:Reply((k:IsServer()and"\060\083\101\114\118\101\114\062"or"\060\067\108\105\101\110\116\062").." \067\111\109\109\097\110\100\115 \119\105\108\108 \110\111\116 \114\117\110 \105\110-\103\097\109\101 \105\102 \110\111 \066\101\102\111\114\101\082\117\110 \104\111\111\107 \105\115 \099\111\110\102\105\103\117\114\101\100\046 \076\101\097\114\110 \109\111\114\101\058 \104\116\116\112\115\058\047\047\101\114\121\110\046\105\111\047\067\109\100\114\047\103\117\105\100\101\047\072\111\111\107\115\046\104\116\109\108",Color3.fromRGB(255,228,26))z=true end else return"\067\111\109\109\097\110\100 \098\108\111\099\107\101\100 \102\111\114 \115\101\099\117\114\105\116\121 \097\115 \110\111 \066\101\102\111\114\101\082\117\110 \104\111\111\107 \105\115 \099\111\110\102\105\103\117\114\101\100\046"end end for k,i in ipairs(l.Registry.Hooks[i])do local l=i.callback(F,...)if l~=nil then return tostring(l)end end end function R.PushHistory(Y,F)assert(k:IsClient(),"\080\117\115\104\072\105\115\116\111\114\121 \109\097\121 \111\110\108\121 \098\101 \117\115\101\100 \102\114\111\109 \116\104\101 \099\108\105\101\110\116\046")local z=Y:GetHistory()if l.TrimString(F)==""or F==z[#z]then return end z[#z+1]=F i:SetTeleportSetting(P,z)end function R.GetHistory(F)assert(k:IsClient(),"\071\101\116\072\105\115\116\111\114\121 \109\097\121 \111\110\108\121 \098\101 \117\115\101\100 \102\114\111\109 \116\104\101 \099\108\105\101\110\116\046")return i:GetTeleportSetting(P)or{}end return function(k)R.Cmdr=k R.Registry=k.Registry return R end
local RunService = game:GetService("RunService")
local TeleportService = game:GetService("TeleportService")
local Players = game:GetService("Players")
local Util = require(script.Parent.Util)
local Command = require(script.Parent.Command)

local HISTORY_SETTING_NAME = "CmdrCommandHistory"
local displayedBeforeRunHookWarning = false

--// The dispatcher handles creating and running commands during the game.
local Dispatcher = {
	Cmdr = nil;
	Registry = nil;
}

--// Takes in raw command information and generates a command out of it.
-- text and executor are required arguments.
-- allowIncompleteData, when true, will ignore errors about arguments missing so we can parse live as the user types.
-- data is for special networked Data about the command gathered on the client. Purely Optional.
-- returns the command if successful, or (false, errorText) if not
function Dispatcher:Evaluate (text, executor, allowIncompleteArguments, data)
	if RunService:IsClient() == true and executor ~= Players.LocalPlayer then
		error("Can't evaluate a command that isn't sent by the local player.")
	end

	local arguments = Util.SplitString(text)
	local commandName = table.remove(arguments, 1)
	local commandObject = self.Registry:GetCommand(commandName)

	if commandObject then
		-- No need to continue splitting when there are no more arguments. We'll just mash any additional arguments into the last one.
		arguments = Util.MashExcessArguments(arguments, #commandObject.Args)

		-- Create the CommandContext and parse it.
		local command = Command.new({
			Dispatcher = self,
			Text = text,
			CommandObject = commandObject,
			Alias = commandName,
			Executor = executor,
			Arguments = arguments,
			Data = data
		})
		local success, errorText = command:Parse(allowIncompleteArguments)

		if success then
			return command
		else
			return false, errorText
		end
	else
		return false, ("%q is not a valid command name. Use the help command to see all available commands."):format(tostring(commandName))
	end
end

--// A helper that evaluates and runs the command in one go.
-- Either returns any validation errors as a string, or the output of the command as a string. Definitely a string, though.
function Dispatcher:EvaluateAndRun (text, executor, options)
	executor = executor or Players.LocalPlayer
	options = options or {}

	if RunService:IsClient() and options.IsHuman then
		self:PushHistory(text)
	end

	local command, errorText = self:Evaluate(text, executor, nil, options.Data)

	if not command then
		return errorText
	end

	local ok, out = xpcall(function()
		local valid, errorText = command:Validate(true) -- luacheck: ignore

		if not valid then
			return errorText
		end

		return command:Run() or "Command executed."
	end, function(value)
		return debug.traceback(tostring(value))
	end)

	if not ok then
		warn(("Error occurred while evaluating command string %q\n%s"):format(text, tostring(out)))
	end

	return ok and out or "An error occurred while running this command. Check the console for more information."
end

--// Send text as the local user to remote server to be evaluated there.
function Dispatcher:Send (text, data)
	if RunService:IsClient() == false then
		error("Dispatcher:Send can only be called from the client.")
	end

	return self.Cmdr.RemoteFunction:InvokeServer(text, {
		Data = data
	})
end

--// Invoke a command programmatically as the local user e.g. from a settings menu
-- Command should be the first argument, all arguments afterwards should be the arguments to the command.
function Dispatcher:Run (...)
	if not Players.LocalPlayer then
		error("Dispatcher:Run can only be called from the client.")
	end

	local args = {...}
	local text = args[1]

	for i = 2, #args do
		text = text .. " " .. tostring(args[i])
	end

	local command, errorText = self:Evaluate(text, Players.LocalPlayer)

	if not command then
		error(errorText) -- We do a full-on error here since this is code-invoked and they should know better.
	end

	local success, errorText = command:Validate(true) -- luacheck: ignore

	if not success then
		error(errorText)
	end

	return command:Run()
end

--// Runs hooks matching name and returns nil for ok or a string for cancellation
function Dispatcher:RunHooks(hookName, commandContext, ...)
	if not self.Registry.Hooks[hookName] then
		error(("Invalid hook name: %q"):format(hookName), 2)
	end

	if
		hookName == "BeforeRun"
		and #self.Registry.Hooks[hookName] == 0
		and commandContext.Group ~= "DefaultUtil"
		and commandContext.Group ~= "UserAlias"
		and commandContext:HasImplementation()
	then

		if RunService:IsStudio() then
			if displayedBeforeRunHookWarning == false then
				commandContext:Reply((RunService:IsServer() and "<Server>" or "<Client>") .. " Commands will not run in-game if no BeforeRun hook is configured. Learn more: https://eryn.io/Cmdr/guide/Hooks.html", Color3.fromRGB(255,228,26))
				displayedBeforeRunHookWarning = true
			end
		else
			return "Command blocked for security as no BeforeRun hook is configured."
		end
	end

	for _, hook in ipairs(self.Registry.Hooks[hookName]) do
		local value = hook.callback(commandContext, ...)

		if value ~= nil then
			return tostring(value)
		end
	end
end

function Dispatcher:PushHistory(text)
	assert(RunService:IsClient(), "PushHistory may only be used from the client.")

	local history = self:GetHistory()

	-- Remove duplicates
	if Util.TrimString(text) == "" or text == history[#history] then
		return
	end

	history[#history + 1] = text

	TeleportService:SetTeleportSetting(HISTORY_SETTING_NAME, history)
end

function Dispatcher:GetHistory()
	assert(RunService:IsClient(), "GetHistory may only be used from the client.")

	return TeleportService:GetTeleportSetting(HISTORY_SETTING_NAME) or {}
end

return function (cmdr)
	Dispatcher.Cmdr = cmdr
	Dispatcher.Registry = cmdr.Registry

	return Dispatcher
end