--Main.lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local container = script.Parent

local Settings = require(container:WaitForChild("Settings"))
local Utility = require(container:WaitForChild("Utility"))
local ClientModule = container:WaitForChild("ClientHandler")
local ClientBooster = container:WaitForChild("ClientBooster")

local RE = ReplicatedStorage:FindFirstChild("ProxialRemote") or Instance.new("RemoteEvent", ReplicatedStorage)
RE.Name = "ProxialRemote"

_G.ProxialCMD = {
	Registry = {};
	Settings = Settings;
	Util = Utility;
	Modules = {};

	Log = function(player, message, msgType)
		msgType = msgType or "INFO"

		local color = Settings.Feedback.Colors.DEFAULT
		if Settings.Feedback.Colors[msgType] then
			color = Settings.Feedback.Colors[msgType]
		end

		local prefix = Settings.Feedback.BasePrefix
		if msgType ~= "INFO" then
			prefix = prefix .. msgType .. " "
		end

		local msg = string.format("%s%s", prefix, message)
		
		if player then
			RE:FireClient(player, "Log", {
				Text = msg,
				Color = color
			})
		end
	end,

	RegisterCommand = function(cmdTable)
		if not (cmdTable.Name and cmdTable.Callback) then return end

		local cmdName = cmdTable.Name:lower()
		_G.ProxialCMD.Registry[cmdName] = cmdTable

		if cmdTable.Aliases then
			for _, alias in ipairs(cmdTable.Aliases) do
				_G.ProxialCMD.Registry[alias:lower()] = cmdTable
			end
		end
	end,
}

local function InitClient(player)
	local playerGui = player:WaitForChild("PlayerGui")
	
	if playerGui:FindFirstChild("ProxialClient") then return end
	
	local container2 = ClientBooster:Clone()
	container2.Name = "ProxialClient"
	
	local moduleClone = ClientModule:Clone()
	moduleClone.Parent = container2
	
	container2.Parent = playerGui
end

local function LoadFolder(fName, cb)
	local folder = container:FindFirstChild(fName)
	if not folder then return end

	for _, mod in ipairs(folder:GetChildren()) do
		if mod:IsA("ModuleScript") then
			local s, res = pcall(require, mod)
			if s then
				if fName == "Commands" and type(res) == "table" and #res > 0 then
					for _, cmd in ipairs(res) do cb(cmd.Name, cmd) end
				else
					cb(mod.Name, res)
				end
			end
		end
	end
end

local function Init()
	LoadFolder("Data", function(n, m)
		_G.ProxialCMD.Modules[n] = m
	end)
	
	task.wait(0.1)

	LoadFolder("Commands", function(_, cmdTable)
		_G.ProxialCMD.RegisterCommand(cmdTable)
	end)

	LoadFolder("Addons", function(name, addon)
		if type(addon) == "function" then
			task.spawn(function()
				local s, err = pcall(addon, _G.ProxialCMD)
				if not s then
					warn("[Proxial] Addon '" .. name .. "' errored: " .. err)
				end
			end)
		else
			warn("[Proxial] Invalid addon format: " .. name)
		end
	end)

	local function SetupPlayer(player)
		local PlayerService = _G.ProxialCMD.Modules["PlayerService"]
		if PlayerService then
			PlayerService.Init(player)
		end
		
		InitClient(player)

		player.Chatted:Connect(function(message)
			local prefixUsed = nil

			for _, p in ipairs(Settings.Prefix) do
				if message:sub(1, #p) == p then prefixUsed = p; break end
			end

			if not prefixUsed then return end

			local content = message:sub(#prefixUsed + 1)
			local split = content:split(" ")
			local cmdName = table.remove(split, 1):lower()
			local cmd = _G.ProxialCMD.Registry[cmdName]

			if cmd and PlayerService then
				local pLevel = PlayerService.GetLevel(player)

				if pLevel >= (cmd.Level or 0) then
					local formattedArgs = Utility.PrarseArguments(player, split)
					
					local success, err = pcall(cmd.Callback, player, formattedArgs)
					if not success then
						_G.ProxialCMD.Log(player, "Error executing command: " .. err, "ERROR")
					end
				else
					_G.ProxialCMD.Log(player, Utility.Format(Settings.Feedback.Messages.NoPermissionMsg, {level = cmd.PermissionLevel}), "ERROR")
				end
			end
		end)
	end
	
	Players.PlayerAdded:Connect(SetupPlayer)
	
	for _, player in ipairs(Players:GetPlayers()) do SetupPlayer(player) end
end

Init()

--ClientBooster.lua
local logic = require(script:WaitForChild("ClientHandler"))

logic.Init()

--ClientHandler.lua
local Players = game:GetService("Players")
local TextChatService = game:GetService("TextChatService")

local Client = {}

function Client.Init(sData)
	local TCS = game:GetService("TextChatService")
	local RS = game:GetService("ReplicatedStorage")
	local RE = RS:WaitForChild("ProxialRemote")
	
	local function getGeneralChannel()
		local channel = TextChatService:FindFirstChild("RBXGeneral", true)
		if channel and channel:IsA("TextChannel") then
			return channel
		end
		
		return nil
	end
	
	RE.OnClientEvent:Connect(function(action, data)
		if action == "Log" then
			local channel = getGeneralChannel()
			
			if channel then
				local hex = data.Color:ToHex()
				local formatted = string.format(
					'<font color="#%s"><b>%s</b></font>',
					hex,
					data.Text
				)
				
				channel:DisplaySystemMessage(formatted, "Proxial.SystemMessage")
			end
		end
	end)
	
	print("[Proxial] ClientHandler initialized")
end

return Client

--Settings.lua
return {
	Prefix = {"$"};
	Levels = {
		[0] = {
			Name = "Player";
			Users = {"*"};
		};
		[2] = {
			Name = "VIP";
			Users = {};
		};
		[3] = {
			Name = "Mod";
			Users = {};
		};
		[4] = {
			Name = "HeadMod";
			Users = {};
		};
		[5] = {
			Name = "Admin";
			Users = {};
		};
		[6] = {
			Name = "HeadAdmin";
			Users = {};
		};
		[7] = {
			Name = "Creator";
			Users = {};
		};
		[8] = {
			Name = "CoHolder";
			Users = {};
		};
		[9] = {
			Name = "Holder";
			Users = {};
		};
		[math.huge] = {
			Name = "GameCreator";
			Users = {};
		};
	};

	-- UI / Feedback Settings
	Feedback = {
		ShowErrorInChat = true, -- If true, tells the player why a command failed
		Messages = {
			NoPermissionMsg = "You do not have the required permission level ({level}) to use this.",
			CommandNotFoundMsg = "Command '{cmd}' not found.",
		};
		Colors = {
			ERROR = Color3.fromRGB(255, 100, 100);
			INFO = Color3.fromRGB(100, 255, 100);
			WARNING = Color3.fromRGB(255, 255, 100);
			DEFAULT = Color3.fromRGB(255, 255, 255);
		};
		BasePrefix = "[Proxial]: ";
	},

	-- Automatic Data Loading
	AutoLoadCommands = true,
	CommandsFolderName = "Commands",
	AddonsFolderName = "Addons"
}

--Utility.lua
local Utility = {}

function Utility.PrarseArguments(speaker, args)
	local parsed = {}
	
	for _, val in ipairs(args) do
		local num = tonumber(val)
		if num then
			table.insert(parsed, num)
			continue
		end
		
		local lowVal = val:lower()
		
		if table.find({"true","yes","on"}, lowVal) then
			table.insert(parsed, true)
			continue
		elseif table.find({"false","no","off"}, lowVal) then
			table.insert(parsed, false)
			continue
		end
		
		if val:find(",") then
			local subTargets = {}
			local names = val:split(",")
			
			for _, name in ipairs(names) do
				local found = Utility.GetPlayers(speaker, name)
				
				for _, p in ipairs(found) do
					if not table.find(subTargets, p) then
						table.insert(subTargets, p)
					end
				end
			end
			
			table.insert(parsed, subTargets)
			continue
		end
		
		local players = Utility.GetPlayers(speaker, val)
		if #players > 0 then
			table.insert(parsed, players)
			continue
		end
		
		table.insert(parsed, val)
	end
	
	return parsed
end

function Utility.GetPlayers(speaker, query)
	if not query or query == "" then return {} end

	query = string.lower(query)
	local found = {}

	if query == "me" then
		table.insert(found, speaker)
	elseif query == "all" then
		for _, p in ipairs(game.Players:GetPlayers()) do table.insert(found, p) end
	elseif query == "others" then
		for _, p in ipairs(game.Players:GetPlayers()) do
			if p ~= speaker then table.insert(found, p) end
		end
	elseif query == "random" then
		local players = game.Players:GetPlayers()
		if #players > 0 then
			table.insert(found, players[math.random(1, #players)])
		end
	elseif string.sub(query, 1, 1) == "@" then
		local cleanQuery = string.sub(query, 2) -- remove @
		for _, p in ipairs(game.Players:GetPlayers()) do
			if string.sub(string.lower(p.Name), 1, #cleanQuery) == cleanQuery then
				table.insert(found, p)
			end
		end
	else
		for _, p in ipairs(game.Players:GetPlayers()) do
			local plrName = string.lower(p.Name)
			local displayName = string.lower(p.DisplayName)

			if plrName:sub(1, #query) == query or displayName:sub(1, #query) == query then
				table.insert(found, p)
			end
		end
	end

	return found
end

function Utility.Format(str, dict)
	for k, v in pairs(dict) do
		str = string.gsub(str, "{" .. k .. "}", tostring(v))
	end
	return str
end

function Utility.CombineNames(instamces)
	local names = {}
	local targets = typeof(instamces) == "table" and instances or {instamces}
	
	for _, obj in ipairs(targets) do
		table.insert(names, obj.Name)
	end
	
	return table.concat(names, ", ")
end

function Utility.GetRankNameByLevel(level, Settings)
	local highestMatch = "Player"
	local highestVal = -1
	
	for lvl, data in pairs(Settings.Levels) do
		if level >= lvl and lvl > highestVal then
			highestMatch = data.Name
			highestVal = lvl
		end
	end
	
	return highestMatch
end

return Utility

--Addons/ExampleAddon.lua
return function(system)
	local settings = system.Settings
	local util = system.Util
	
	system.RegisterCommand({
		Name = "exampleCommand";
		Aliases = {"example"};
		Description = "This is an example command!";
		PermissionLevel = 1;
		Callback = function(context, args)
			print("HELLO WORLD FROM AN EXAMPLE ADDON!")
		end;
	})
end

--Commands/Commands.lua
--[[
Name
	Nane of the command, will be used in the command prefix. (example: !explode)

Aliases
	Alternate names for the command. (example: !explode, !boom, !nuke)

Description
	A description of the command and what it does.
	Displayed in the command list.
	
PermissionLevel
	An number representing the minimum permission level required to use the command.
	0: Player
	1: VIPs
	2: Moderators
	3: Head Moderators
	4: Admins
	5: Head Admins
	6: Creators
	7: Owners
	8: Super Owners
	math.huge: Game Creator

Contributors
	Contributors for the command. (example: {"Roblox"})

Callback
	The function called when the command is executed.
	Parameters:
		speaker - A player who executed the command.
		args - Command arguments. (eg. args[1] = Player)

]]

return {
	{
		Name = "cmds";
		Aliases = {"commands", "cmds", "cmdlist"};
		Description = "Lists all commands you have access to.";
		PermissionLevel = 0;
		Contributors = {"p0x38"};
		LevelLock = false;
		Callback = function(speaker, args)
			local ListService = _G.ProxialCMD.Modules["ListService"]
			local available = ListService.GetAvailableCommands(speaker)
			
			_G.ProxialCMD.Log(speaker, "Available Commands:", "INFO")
			
			local listStr = ""
			for _, cmd in ipairs(available) do
				listStr = listStr .. cmd.Name .. (i == #available and "" or ", ")
				if #listStr > 80 then
					_G.ProxialCMD.Log(speaker, listStr, "Default")
					listStr = ""
				end
			end
			
			if listStr ~= "" then
				_G.ProxialCMD.Log(speaker, listStr, "Default")
			end
			
			_G.ProxialCMD.Log(speaker, "Type %info [cmd] for details.", "INFO")
		end,
	};
	{
		Name = "info";
		Aliases = {"commandinfo", "command_info", "cmdinfo"};
		Description = "Displays information about a specific command.";
		PermissionLevel = 0;
		Contributors = {"p0x38"};
		LevelLock = false;
		Callback = function(speaker, args)
			local query = tostring(args[1] or ""):lower()
			local cmd = _G.ProxialCMD.Registry[query]
			
			if cmd then
				_G.ProxialCMD.Log(speaker, "Command: " .. cmd.Name:upper(), "INFO")
				_G.ProxialCMD.Log(speaker, "Description: " .. (cmd.Description or "No description."), "DEFAULT")
				_G.ProxialCMD.Log(speaker, "Level Required: " .. (cmd.PermissionLevel or 0), "DEFAULT")
				if cmd.Aliases and #cmd.Aliases > 0 then
					_G.ProxialCMD.Log(speaker, "Aliases: " .. table.concat(cmd.Aliases, ", "), "DEFAULT")
				end
			else
				_G.ProxialCMD.Log(speaker, "Command not found.", "ERROR")
			end
		end,
	};
	{
		Name = "admins";
		Aliases = {"staff", "online"};
		Description = "Lists all staff members currently online.";
		PermissionLevel = 0;
		Callback = function(speaker, args)
			local ListService = _G.ProxialCMD.Modules["ListService"]
			local staffMembers = ListService.GetOnlineStaff()

			_G.ProxialCMD.Log(speaker, "--- Online Staff ---", "INFO")

			for _, data in ipairs(staffMembers) do
				local text = string.format("[%s] %s", data.Rank, data.Name)
				_G.ProxialCMD.Log(speaker, text, "DEFAULT")
			end
		end
	};
	{
	Name = "explode";
	Aliases = {"boom", "bomb"};
	Description = "Explodes a player or set of players.";
	PermissionLevel = 2;
	Contributors = {"p0x38"};
	LevelLock = false;
	Callback = function(speaker, args)
		for _, player in ipairs(args[1]) do
			local target = player.Character
			if not target then continue end
			
			local explosion = Instance.new("Explosion")
			explosion.Position = target.HumanoidRootPart.Position
			explosion.Parent = target.HumanoidRootPart
			explosion.BlastRadius = 10
			explosion.BlastPressure = 500000
			explosion.DestroyJointRadiusPercent = 0
			explosion.ExplosionType = Enum.ExplosionType.NoCraters
			explosion.Visible = true

			if target.Character then
				local hum = target:FindFirstChild("Humanoid")
				if hum then
					hum.Health = 0
				end
			end
		end
	end,
	};
}

--Data/ListService.lua
local ListService = {}
local Settings = require(script.Parent.Parent:WaitForChild("Settings"))

function ListService.GetOnlineStaff()
	local staff = {}
	local PlayerService = _G.ProxialCMD.Modules["PlayerService"]
	
	for _, player in ipairs(game.Players:GetPlayers()) do
		local lvl = PlayerService.GetLevel(player)
		
		if lvl > 0 then
			table.insert(staff, {
				Name = player.Name,
				Level = lvl,
				Rank = PlayerService.GetRankNameByLevel(lvl, Settings)
			})
		end
	end
	
	table.sort(staff, function(a, b) return a.Level > b.Level end)
	return staff
end

function ListService.GetAvailableCommands(player)
	local PlayerService = _G.ProxialCMD.Modules["PlayerService"]
	local pLevel = PlayerService.GetLevel(player)
	local available = {}
	
	local seen = {}
	
	for _, cmd in pairs(_G.ProxialCMD.Registry) do
		if not seen[cmd.Name] and pLevel >= (cmd.PermissionLevel or 0) then
			table.insert(available, cmd)
			seen[cmd.Name] = true
		end
	end
	
	table.sort(available, function(a, b) return a.Name < b.Name end)
	return available
end

--Data/PlayerService.lua
local PlayerService = {}
local Settings = require(script.Parent.Parent:WaitForChild("Settings"))

function PlayerService.Init(player)
	local level = 0
	
	for lvl, data in pairs(Settings.Levels) do
		for _, userIdentifier in ipairs(data.Users) do
			if userIdentifier == "*" or userIdentifier:lower() == player.Name:lower() or tostring(player.UserId) == userIdentifier then
				if lvl > level then level = lvl end
			end
		end
	end
	
	if player.UserId == game.CreatorId then level = math.huge end
	player:SetAttribute("PermissionLevel", level)
end

function PlayerService.GetLevel(player)
	return player:GetAttribute("PermissionLevel") or 0
end

return PlayerService