--// Init

return service.NewProxy({
	__call = function(tab, data)
		local mutex = service.RunService:FindFirstChild("__Adonis_MODULE_MUTEX")
		if mutex then
			warn("\n-----------------------------------------------"
				.."\nAdonis server-side is already running! Aborting..."
				.."\n-----------------------------------------------")
			script:Destroy()
			return "FAILED"
		else
			mutex = service.New("StringValue", {Name = "__Adonis_MODULE_MUTEX", Value = "Running"})
			local mutexBackup = mutex:Clone()
			local function makePersistent(m)
				local connection1, connection2 = nil, nil
				connection1 = m:GetPropertyChangedSignal("Parent"):Connect(function()
					if not m or m.Parent ~= service.RunService then
						connection1:Disconnect()
						connection2:Disconnect()
						warn("Adonis module mutex removed; Regenerating...")
						makePersistent(mutexBackup)
						mutexBackup.Parent = service.RunService
						mutexBackup = mutexBackup:Clone()
					end
				end)
				connection2 = m:GetPropertyChangedSignal("Name"):Connect(function()
					if m and m.Name ~= "__Adonis_MODULE_MUTEX" then
						warn("Adonis module mutex renamed; Refreshing...")
						m.Name = "__Adonis_MODULE_MUTEX"
					end
				end)
			end
			makePersistent(mutex)
			mutex.Parent = service.RunService
		end

		--// Begin Script Loading
		setfenv(1, setmetatable({}, {__metatable = unique}))
		data = service.Wrap(data or {})

		if not data or not data.Loader then
			warn("WARNING: MainModule loaded without using the loader;")
		end

		--// Server Variables
		local setTab = require(server.Deps.DefaultSettings)
		server.Defaults = setTab
		server.Settings = data.Settings or setTab.Settings or {}
		-- For some reason line 540 errors because CloneTable is nil
		local function CloneTable(tab, recursive)
			local clone = table.clone(tab)
		
			if recursive then
				for i,v in pairs(clone) do
					if type(v) == "table" then
						clone[i] = CloneTable(v, recursive)
					end
				end
			end
		
			return clone
		end
		server.OriginalSettings = CloneTable(server.Settings, true)
		server.Descriptions = data.Descriptions or setTab.Descriptions or {}
		server.Messages = data.Messages or setTab.Settings.Messages or {}
		server.Order = data.Order or setTab.Order or {}
		server.Data = data or {}
		server.Model = data.Model or service.New("Model")
		server.ModelParent = data.ModelParent or service.ServerScriptService;
		server.Dropper = data.Dropper or service.New("Script")
		server.Loader = data.Loader or service.New("Script")
		server.Runner = data.Runner or service.New("Script")
		server.LoadModule = LoadModule
		server.LoadPackage = LoadPackage
		server.ServiceSpecific = ServiceSpecific

		server.Shared = Folder.Shared
		server.ServerPlugins = data.ServerPlugins
		server.ClientPlugins = data.ClientPlugins
		server.Client = Folder.Parent.Client

		locals.Settings = server.Settings
		locals.CodeName = server.CodeName

		--// THIS NEEDS TO BE DONE **BEFORE** ANY EVENTS ARE CONNECTED
		if server.Settings.HideScript and data.Model then
			data.Model.Parent = nil
			script:Destroy()
		end

		--// Copy client themes, plugins, and shared modules to the client folder
		local packagesToRunWithPlugins = {};
		local shared = service.New("Folder", {
			Name = "Shared";
			Parent = server.Client;
		})

		for index, module in ipairs(Folder.Shared:GetChildren()) do
			module:Clone().Parent = shared;
		end

		for index,plugin in pairs(data.ClientPlugins or {}) do
			plugin:Clone().Parent = server.Client.Plugins;
		end

		for index,theme in pairs(data.Themes or {}) do
			theme:Clone().Parent = server.Client.UI;
		end

		for index,pkg in pairs(data.Packages or {}) do
			LoadPackage(pkg, Folder.Parent, false);
		end

		for setting,value in pairs(server.Defaults.Settings) do
			if server.Settings[setting] == nil then
				server.Settings[setting] = value
			end
		end

		for desc,value in pairs(server.Defaults.Descriptions) do
			if server.Descriptions[desc] == nil then
				server.Descriptions[desc] = value
			end
		end

		--// Bind cleanup
		service.DataModel:BindToClose(CleanUp)
		--server.CleanUp = CleanUp;

		--// Require some dependencies
		server.Threading = require(server.Deps.ThreadHandler)
		server.Changelog = require(server.Shared.Changelog)
		server.Credits = require(server.Shared.Credits)
		do
			local MaterialIcons = require(server.Shared.MatIcons)
			server.MatIcons = setmetatable({}, {
				__index = function(self, ind)
					local materialIcon = MaterialIcons[ind]
					if materialIcon then
						self[ind] = string.format("rbxassetid://%d", materialIcon)
						return self[ind]
					end
				end,
				__metatable = "Adonis"
			})
		end


		--// Load services
		for ind, serv in ipairs(ServicesWeUse) do local temp = service[serv] end

		--// Load core modules
		for ind,load in ipairs(LoadingOrder) do
			local CoreModule = Folder.Core:FindFirstChild(load)
			if CoreModule then
				LoadModule(CoreModule, true, nil, nil, true) --noenv, CoreModule
			end
		end

		--// Server Specific Service Functions
		ServiceSpecific.GetPlayers = server.Functions.GetPlayers

		--// Initialize Cores
		local runLast = {}
		local runAfterInit = {}
		local runAfterPlugins = {}

		for i,name in ipairs(LoadingOrder) do
			local core = server[name]

			if core then
				if type(core) == "table" or (type(core) == "userdata" and getmetatable(core) == "ReadOnly_Table") then
					if core.RunLast then
						table.insert(runLast, core.RunLast);
						core.RunLast = nil;
					end

					if core.RunAfterInit then
						table.insert(runAfterInit, core.RunAfterInit);
						core.RunAfterInit = nil;
					end

					if core.RunAfterPlugins then
						table.insert(runAfterPlugins, core.RunAfterPlugins);
						core.RunAfterPlugins = nil;
					end

					if core.Init then
						core.Init(data);
						core.Init = nil;
					end
				end
			end
		end

		--// Variables that rely on core modules being initialized
		server.Logs.Errors = ErrorLogs

		--// Load any afterinit functions from modules (init steps that require other modules to have finished loading)
		for i,f in pairs(runAfterInit) do
			f(data);
		end

		--// Load Plugins
		for index,plugin in ipairs(server.PluginsFolder:GetChildren()) do
			LoadModule(plugin, false, {script = plugin}, true); --noenv
		end

		for index,plugin in pairs(data.ServerPlugins or {}) do
			LoadModule(plugin, false, {script = plugin});
		end

		--// We need to do some stuff *after* plugins are loaded (in case we need to be able to account for stuff they may have changed before doing something, such as determining the max length of remote commands)
		for i,f in pairs(runAfterPlugins) do
			f(data);
		end

		--// Below can be used to determine when all modules and plugins have finished loading; service.Events.AllModulesLoaded:Connect(function() doSomething end)
		server.AllModulesLoaded = true;
		service.Events.AllModulesLoaded:Fire(os.time());

		--// Queue handler
		--service.StartLoop("QueueHandler","Heartbeat",service.ProcessQueue)

		--// Stuff to run after absolutely everything else has had a chance to run and initialize and all that
		for i,f in pairs(runLast) do
			f(data);
		end

		if data.Loader then
			warn("Loading Complete; Required by "..tostring(data.Loader:GetFullName()))
		else
			warn("Loading Complete; No loader location provided")
		end

		if server.Logs then
			server.Logs.AddLog(server.Logs.Script, {
				Text = "Finished Loading";
				Desc = "Adonis finished loading";
			})
		else
			warn("SERVER.LOGS TABLE IS MISSING. THIS SHOULDN'T HAPPEN! SOMETHING WENT WRONG WHILE LOADING CORE MODULES(?)");
		end
		service.Events.ServerInitialized:Fire();

		return "SUCCESS"
	end;
	__tostring = function()
		return "Adonis"
	end;
	__metatable = "Adonis";
})
