--!optimize 2

--[[
    ClientEvent.lua
    Handles client-side event operations, with type validation and optimized networking
    Author: Asta (@TheYusufGamer)
]]

-- Type Imports
local ClientManagerModule = require(script.Parent.Parent.Client.ClientManager)
local TypeChecker = require(script.Parent.Parent.Shared.TypeChecker)
local DynamicSender = require(script.Parent.Parent.Shared.DynamicSender)
local CircuitBreakerModule = require(script.Parent.Parent.Shared.CircuitBreaker)
local Errors = require(script.Parent.Parent.Shared.Errors)
local Constants = require(script.Parent.Parent.Shared.Constants)

-- Assume necessary types are exported or defined
type ClientManager = ClientManagerModule.ClientManager
type ClientEventOptions = ClientManagerModule.ClientEventOptions
type TypeCheckerResult = TypeChecker.TypeCheckerResult -- e.g., {success: boolean, error: string?}
type CircuitBreaker = CircuitBreakerModule.CircuitBreaker

-- Define the type for the callback used in OnEvent
type ClientEventHandler = (data: any) -> ()

-- Type definition for the ClientEvent *implementation*
-- This aligns with the client-side aspects of the NetRayEvent unified type.
export type ClientEventType = {
	-- Properties matching NetRayEvent
	Name: string,
	Options: ClientEventOptions,
	RemoteEvent: RemoteEvent, -- Should always exist after successful .new()
	TypeDefinition: any?,
	Priority: number, -- Used for queuing incoming events

	-- Client-Specific Implementation Details
	ClientManager: ClientManager,

	-- Methods matching NetRayEvent (Client-side subset + OnEvent)
	-- Note: Using ClientEventType here for 'self' as it's the internal implementation type
	OnEvent: (self: ClientEventType, callback: ClientEventHandler) -> ClientEventType, -- Returns self for chaining
	FireServer: (self: ClientEventType, data: any) -> (),

	-- Internal Helper Methods
	SetupListeners: (self: ClientEventType) -> (),
}

-- Define the ClientEvent module table
local ClientEvent = {}
ClientEvent.__index = ClientEvent

-- Constructor
function ClientEvent.new(eventName: string, options: ClientEventOptions?, clientManager: ClientManager): ClientEventType? -- Return optional as registration might fail
	options = options or {}
	local self: ClientEventType = setmetatable({}, ClientEvent)

	self.Name = eventName
	self.Options = options
	self.ClientManager = clientManager

	-- Register internally with the ClientManager to get RemoteEvent and setup CBs
	-- Using the renamed internal method
	local eventInfo = clientManager:_InternalRegisterEvent(eventName, self.Options)
	if not eventInfo or not eventInfo.Remote then
		warn("[NetRay ClientEvent] Failed to register/find RemoteEvent for: " .. eventName .. ". Event functionality disabled.")
		-- Cannot proceed without RemoteEvent, return nil
		return nil
	end

	self.RemoteEvent = eventInfo.Remote :: RemoteEvent -- Assign the found RemoteEvent

	-- Store a reference to this ClientEvent instance within the manager's info
	-- This allows retrieval via GetEvent without creating a new instance each time.
	eventInfo.Event = self

	-- Store type definitions
	self.TypeDefinition = options.typeDefinition

	-- Store priority for incoming event processing
	self.Priority = options.priority or Constants.PRIORITY.NORMAL or 2 -- Default to NORMAL

	-- Set up listeners *after* all essential properties are set
	self:SetupListeners()

	return self :: ClientEventType -- Cast ensures return type matches annotation
end

-- Internal method to set up RemoteEvent listener
function ClientEvent:SetupListeners()
	self.RemoteEvent.OnClientEvent:Connect(function(encodedData: any, instances)
		-- 1. Decode incoming data
		local decodeSuccess, decodedData = pcall(function()
			return DynamicSender:DecodeReceivedData(encodedData,instances)
		end)

		if not decodeSuccess then
			-- decodeSuccess is false, decodedData contains the error
			warn(("[NetRay ClientEvent] Failed to decode data for event '%s' from server: %s"):format(self.Name, tostring(decodedData)))
			self.ClientManager.Signals.Error:Fire("DecodeError", self.Name, decodedData)
			return
		end
		-- decodedData now holds the actual payload (or array of payloads)

		-- Get event info from manager (should exist)
		local eventInfo = self.ClientManager.Events[self.Name]
		if not eventInfo or not eventInfo.Handlers then
			warn(("[NetRay ClientEvent] Received event '%s' but no info/handlers found in manager."):format(self.Name))
			return -- Should not happen if .new succeeded
		end
		local handlers = eventInfo.Handlers :: {ClientEventHandler} -- Assert type

		-- Function to process a single event payload (handles validation, middleware, CB, enqueueing)
		local function processSingleEvent(data: any): boolean
			-- 2. Type Checking
			if self.TypeDefinition then
				local typeCheckResult: TypeCheckerResult = TypeChecker:Validate(data, self.TypeDefinition)
				if not typeCheckResult.success then
					warn(("[NetRay ClientEvent] Incoming type validation failed for event '%s': %s"):format(
						self.Name, typeCheckResult.error or "Unknown error"))
					self.ClientManager.Signals.Error:Fire("TypeValidationError", self.Name, data, typeCheckResult.error)
					return false -- Stop processing this specific payload
				end
			end

			-- 3. Middleware (Incoming)
			local continueProcessing, processedData = self.ClientManager:InvokeMiddleware(self.Name, data)
			if not continueProcessing then
				if self.ClientManager.DebugEnabled then
					print(("[NetRay ClientEvent] Middleware blocked incoming event '%s'"):format(self.Name))
				end
				return false -- Stop processing this specific payload
			end
			local finalData = if processedData ~= nil then processedData else data

			-- 4. Circuit Breaker Check
			local circuitBreaker = self.ClientManager.CircuitBreakers[self.Name .. "_Handler"] -- Use distinct name
			if circuitBreaker and not circuitBreaker:IsAllowed() then
				if self.ClientManager.DebugEnabled then
					print(("[NetRay ClientEvent] Circuit breaker '%s' open, preventing handler execution."):format(self.Name .. "_Handler"))
				end
				-- Attempt fallback if defined
				if typeof(circuitBreaker.Options.fallback) == "function" then
					pcall(circuitBreaker.Options.fallback, finalData) -- Safely call fallback
				end
				-- Don't queue the message if CB is open
				return false -- Indicate failure to queue/process
			end
			local finalData = data
			-- 5. Enqueue Handler Execution
			if #handlers == 0 then
				if self.ClientManager.DebugEnabled then
					print(("[NetRay ClientEvent] Event '%s' received, but no OnEvent handlers are attached."):format(self.Name))
				end
				-- Still counts as successful processing up to this point if no handler needed
			else
				self.ClientManager.Signals.EventFired:Fire(self.Name, finalData) -- Fire signal before queueing
				for _, handler in ipairs(handlers) do
					-- Enqueue the actual handler call with the final data
					self.ClientManager:EnqueueMessage(
						self.Priority, -- Use the event's priority for queueing
						function() -- Wrap handler call to manage CB state
							local handlerSuccess = pcall(handler, finalData)
							-- Record CB success/failure *after* handler executes (if CB exists)
							if circuitBreaker then
								if handlerSuccess then
									circuitBreaker:RecordSuccess()
								else
									circuitBreaker:RecordFailure()
								end
							end
						end
						-- No args needed for the wrapper function itself
					)
				end
			end

			-- If CB exists and was allowed, we count this step (enqueueing) as success
			-- The actual handler success/failure updates the CB later.
			return true -- Indicate successful enqueueing/processing step
		end -- End of processSingleEvent

		-- Check if decoded data is likely a batch (array of tables/values)
		-- Relying on DynamicSender's potential batching mechanism.
		if typeof(decodedData) == "table" and #decodedData > 0 and typeof(decodedData[1]) ~= "nil" then
			-- Heuristic: Treat as a batch if it's a non-empty array.
			if self.ClientManager.DebugEnabled then
				print(("[NetRay ClientEvent] Processing %d batched events for '%s'"):format(#decodedData, self.Name))
			end

			for _, eventData in ipairs(decodedData :: {any}) do
				processSingleEvent(eventData) -- Process each item
				-- If one item fails (e.g., type validation), it doesn't stop others
				-- CB state might change mid-batch based on handler execution from previous items.
			end
		else
			-- Treat as a single event
			processSingleEvent(decodedData)
		end
	end)
end

-- Method to register a handler for this event
function ClientEvent:OnEvent(callback: ClientEventHandler): ClientEventType
	if typeof(callback) ~= "function" then
		error("[NetRay ClientEvent] OnEvent expects a function(data) as its argument", 2)
		-- error stops execution, no return needed
	end

	-- Add callback to the Handlers list stored in the ClientManager
	local eventInfo = self.ClientManager.Events[self.Name]
	if not eventInfo then
		-- This should ideally not happen if .new succeeded, means internal state issue.
		error("[NetRay ClientEvent] Cannot call OnEvent for '"..self.Name.."': Event info missing in ClientManager.", 2)
	end
	-- Ensure Handlers array exists (should be guaranteed by _InternalRegisterEvent)
	eventInfo.Handlers = eventInfo.Handlers or {} :: {ClientEventHandler}

	table.insert(eventInfo.Handlers, callback)

	return self -- Return self for chaining
end

-- Method to fire the event to the server
function ClientEvent:FireServer(data: any)
	-- 1. Check Throttling before proceeding
	if not self.ClientManager:CheckOutgoingThrottle() then
		warn(("[NetRay ClientEvent] FireServer throttled for event '%s': too many outgoing requests."):format(self.Name))
		-- Potentially fire a signal or handle differently? For now, just warn and return.
		return
	end

	-- 2. Circuit Breaker Check (Outgoing)
	-- Check if the circuit is open before attempting to send.
	local circuitBreaker = self.ClientManager.CircuitBreakers[self.Name] -- Assuming CB uses event name
	if circuitBreaker and not circuitBreaker:IsAllowed() then
		if self.ClientManager.DebugEnabled then
			print(("[NetRay ClientEvent] Circuit breaker open for '%s', blocking FireServer."):format(self.Name))
		end
		-- Attempt fallback if defined
		if typeof(circuitBreaker.Options.fallback) == "function" then
			pcall(circuitBreaker.Options.fallback, data) -- Safely call fallback
		end
		-- Don't record failure here, the circuit is already open.
		return -- Stop execution
	end
	-- If circuitBreaker exists and IS allowed, we proceed.

	local sendSuccess = false -- Track if the send operation itself succeeds
	local sendError = nil -- Store potential error from type/middleware/send

	local success, err = pcall(function() -- Wrap potentially erroring steps
		-- 3. Type Checking (Outgoing Data)
		if self.TypeDefinition then
			local typeCheckResult: TypeCheckerResult = TypeChecker:Validate(data, self.TypeDefinition)
			if not typeCheckResult.success then
				-- Throw a specific error to be caught by pcall
				error(Errors.new("Validation", ("Outgoing type validation failed for event '%s': %s"):format(
					self.Name, typeCheckResult.error or "Unknown error"), { eventName = self.Name }))
			end
		end

		if not data then
			self.RemoteEvent:FireServer()
			return
		end

		-- 4. Middleware (Outgoing)
		local continueProcessing, processedData = self.ClientManager:InvokeMiddleware(self.Name, data)
		if not continueProcessing then
			if self.ClientManager.DebugEnabled then
				print(("[NetRay ClientEvent] Middleware blocked outgoing event '%s'"):format(self.Name))
			end
			-- Treat middleware block as a reason not to send, but not necessarily a "failure" for CB
			error("MiddlewareBlocked") -- Special marker
		end
		local finalData = if processedData ~= nil then processedData else data

		-- 5. Send using DynamicSender
		-- DynamicSender handles serialization, compression (if configured), batching.
		-- It doesn't typically throw errors for network issues itself, but might for bad input

		DynamicSender:Send(self.RemoteEvent, finalData,nil ,{batchable = false})

		-- If we reach here, the send call itself didn't error immediately.
		sendSuccess = true -- Mark send attempt as successful *within pcall*
	end)

	-- Handle results of the pcall
	if not success then
		sendError = err -- Store the error object/message
		-- Check if it was just a middleware block
		if sendError == "MiddlewareBlocked" then
			-- Don't log an error, it was intentionally blocked
			sendError = nil -- Clear the error
		else
			-- Log actual errors (validation, internal DynamicSender error?)
			warn(("[NetRay ClientEvent] Error during FireServer for event '%s': %s"):format(self.Name, tostring(sendError)))
			self.ClientManager.Signals.Error:Fire(sendError, "FireServerError", self.Name)
		end
	end

	-- 6. Record Circuit Breaker Success/Failure (if CB exists)
	if circuitBreaker then
		if sendSuccess then
			circuitBreaker:RecordSuccess()
		elseif sendError ~= nil then -- Only record failure if there was an actual error (not middleware block)
			circuitBreaker:RecordFailure()
		end
	end

	-- 7. Fire Signal (optional, indicates an attempt was made)
	if sendSuccess then
		self.ClientManager.Signals.RequestSent:Fire(self.Name, data) -- Use RequestSent signal
	end

	-- Debug log (adjust based on what DynamicSender:Send actually returns, if anything)
	if self.ClientManager.DebugEnabled and sendSuccess then
		print(("[NetRay ClientEvent] Event %s fired to server."):format(self.Name))
	elseif self.ClientManager.DebugEnabled and sendError == "MiddlewareBlocked" then
		print(("[NetRay ClientEvent] Event %s blocked by middleware during FireServer."):format(self.Name))
	elseif self.ClientManager.DebugEnabled and sendError then
		print(("[NetRay ClientEvent] Event %s failed to fire: %s"):format(self.Name, tostring(sendError)))
	end
end

return ClientEvent


--!optimize 2

--[[
    ClientManager.lua
    Manages client-side networking operations and event registration
    Author: Asta (@TheYusufGamer)
]]

local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Type Imports
local MiddlewareModule = require(script.Parent.Parent.Shared.Middleware)
local QueueModule = require(script.Parent.Parent.Shared.Queue)
local CircuitBreakerModule = require(script.Parent.Parent.Shared.CircuitBreaker)
local Constants = require(script.Parent.Parent.Shared.Constants)
local SignalPlusModule = require(script.Parent.Parent.ThirdParty.SignalPlus)

-- Assume modules export types or you define them
type Middleware = MiddlewareModule.Middleware
type Queue<T> = QueueModule.Queue<T>
type CircuitBreaker = CircuitBreakerModule.CircuitBreaker
type SignalPlus = SignalPlusModule.SignalPlus

-- Type for the message stored in the queue (same as server)
type QueuedMessage = {
	callback: (...any) -> (),
	args: {any}
}

-- Type for ClientEvent options (might differ slightly from ServerEventOptions)
-- Primarily used internally when ClientEvent/RequestClient are created/retrieved.
export type ClientEventOptions = {
	-- Client generally doesn't define rate limits, but might have CB config
	circuitBreaker: {
		failureThreshold: number?,
		resetTimeout: number?,
		fallback: ((...any) -> (...any))?
	}?,
	priority: number?, -- For incoming message processing
	typeDefinition: any?, -- For incoming data validation
	[string]: any
}

-- Internal type for storing info about events the client is aware of
-- Populated potentially by ClientEvent/RequestClient initialization
type ClientRegisteredEventInfo = {
	Remote: RemoteEvent?, -- Optional initially until found
	Options: ClientEventOptions,
	Handlers: { (...any) -> () }, -- List of callbacks attached via ClientEvent:OnEvent
	Event: any? -- Placeholder for the actual ClientEvent instance associated
}

-- Internal type for storing info about requests the client can handle/send
type ClientRequestHandlerInfo = {
	Remote: RemoteFunction?, -- Optional initially until found
	Options: any, -- Define RequestClientOptions later
	Handler: ((...any) -> any)?, -- Callback attached via RequestClient:OnRequest
	Request: any? -- Placeholder for RequestClient instance
}

-- Type for the OutgoingRequests state
type OutgoingRequestState = {
	Count: number,
	LastReset: number,
	Limit: number,
	Burst: {
		Count: number,
		LastReset: number,
		Limit: number
	}
}

-- Type definition for the ClientManager Signals table
type ClientManagerSignals = {
	EventRegistered: SignalPlus, -- (eventName: string) - When GetEvent first finds/creates wrapper
	EventFired: SignalPlus,      -- (eventName: string, data: any) - When handler is invoked
	RequestSent: SignalPlus,     -- (requestName: string, data: any) - C->S request sent
	RequestReceived: SignalPlus, -- (requestName: string, data: any) - S->C request received
	ThrottleExceeded: SignalPlus,-- (limitType: "global" | "burst", count: number, limit: number)
	CircuitBroken: SignalPlus,   -- (eventName: string)
	CircuitReset: SignalPlus,    -- (eventName: string)
	Error: SignalPlus            -- (err: any, context: string?)
}

-- Type for the ClientManager class itself
export type ClientManager = {
	-- Properties
	RemoteFolder: Folder?, -- Optional until Initialize/WaitForChild completes
	Events: {[string]: ClientRegisteredEventInfo}, -- Map eventName to info
	RequestHandlers: {[string]: ClientRequestHandlerInfo}, -- Map requestName to info
	Middleware: Middleware,
	OutgoingRequests: OutgoingRequestState,
	MessageQueues: {[number]: Queue<QueuedMessage>}, -- Map priority level to Queue
	CircuitBreakers: {[string]: CircuitBreaker}, -- Map eventName/requestName to CircuitBreaker instance
	Signals: ClientManagerSignals,
	DebugEnabled: boolean?, -- Added based on potential usage

	-- Methods
	Initialize: (self: ClientManager) -> ClientManager,
	StartQueueProcessing: (self: ClientManager) -> (),
	ProcessMessage: (self: ClientManager, message: QueuedMessage?) -> (),
	GetRemoteEvent: (self: ClientManager, eventName: string) -> RemoteEvent?, -- Returns optional RemoteEvent
	GetRemoteFunction: (self: ClientManager, eventName: string) -> RemoteFunction?, -- Returns optional RemoteFunction
	_InternalRegisterEvent: (self: ClientManager, eventName: string, options: ClientEventOptions?) -> ClientRegisteredEventInfo?,
	CheckOutgoingThrottle: (self: ClientManager) -> boolean,
	EnqueueMessage: (self: ClientManager, priority: number, callback: (...any) -> (), ...any) -> (),
	InvokeMiddleware: (self: ClientManager, eventName: string, data: any) -> (boolean, any?), -- Returns continueProcessing, potentially modifiedData

	-- Constructor
	new: () -> ClientManager
}

-- Constants (use values from Constants module if available)
local PROCESS_LIMITS = Constants.PROCESS_LIMITS or {
	[0] = math.huge, [1] = 5, [2] = 3, [3] = 2, [4] = 1
}
local MIN_PROCESS_TIME = Constants.MIN_PROCESS_TIME or 0.005
local CYCLE_WAIT = Constants.CYCLE_WAIT or 0.01

-- Define the ClientManager module table
local ClientManagerImpl = {}
ClientManagerImpl.__index = ClientManagerImpl

-- Constructor
function ClientManagerImpl.new(): ClientManager
	local self = setmetatable({}, ClientManagerImpl)

	self.RemoteFolder = nil
	self.Events = {} :: {[string]: ClientRegisteredEventInfo}
	self.RequestHandlers = {} :: {[string]: ClientRequestHandlerInfo} -- Type assertion needed
	self.Middleware = MiddlewareModule.new() :: Middleware -- Cast if .new isn't typed
	self.OutgoingRequests = {
		Count = 0,
		LastReset = tick(),
		Limit = Constants.RATE_LIMIT.DEFAULT_MAX_REQUESTS or 120, -- Use Constants if defined
		Burst = {
			Count = 0,
			LastReset = tick(),
			Limit = Constants.RATE_LIMIT.DEFAULT_BURST_LIMIT or 20 -- Use Constants if defined
		}
	} :: OutgoingRequestState
	self.MessageQueues = { -- Type hint for the table keys/values
		[0] = QueueModule.new(),
		[1] = QueueModule.new(),
		[2] = QueueModule.new(),
		[3] = QueueModule.new(),
		[4] = QueueModule.new()
	} :: {[number]: Queue<QueuedMessage>}
	self.CircuitBreakers = {} :: {[string]: CircuitBreaker}
	self.Signals = {
		EventRegistered = SignalPlusModule(),
		EventFired = SignalPlusModule(),
		RequestSent = SignalPlusModule(),
		RequestReceived = SignalPlusModule(),
		ThrottleExceeded = SignalPlusModule(),
		CircuitBroken = SignalPlusModule(),
		CircuitReset = SignalPlusModule(),
		Error = SignalPlusModule()
	} :: ClientManagerSignals
	self.DebugEnabled = false -- Initialize if needed

	return self :: ClientManager
end

function ClientManagerImpl:Initialize(): ClientManager
	-- Get the remote events folder, wait briefly
	local success, remoteFolder = pcall(function()
		return ReplicatedStorage:WaitForChild("NetRayRemotes", 10)
	end)

	if success and remoteFolder and remoteFolder:IsA("Folder") then
		self.RemoteFolder = remoteFolder
	else
		warn("[NetRay ClientManager] Could not find NetRayRemotes Folder in ReplicatedStorage within 10s. Network features might be delayed or fail.")
		self.RemoteFolder = nil -- Ensure it's nil if not found
	end

	-- Initialize queue processing
	self:StartQueueProcessing()

	-- Start request throttling reset loops
	task.spawn(function()
		while true do
			task.wait(1)
			self.OutgoingRequests.Count = 0
			-- Don't reset LastReset here, CheckOutgoingThrottle compares against current time
		end
	end)
	task.spawn(function()
		while true do
			task.wait(0.1) -- 100ms
			self.OutgoingRequests.Burst.Count = 0
			-- Don't reset LastReset here
		end
	end)

	return self
end

function ClientManagerImpl:StartQueueProcessing()
	task.spawn(function()
		while true do

			local cycleStartTime = tick()
			local processedAny = false
		--	print(os.time(), "[Queue] Cycle Start") -- Optional: Keep if needed

			-- Process Critical (all)
			local criticalQueue = self.MessageQueues[0]
			while not criticalQueue:IsEmpty() do
				local message = criticalQueue:Dequeue()
				-- Safer Debug Print
			--	print(os.time(), string.format("[Mgr Proc] Cycle Critical. Dequeued item:"))
			--	print("  ---> Message Content:", message)
				self:ProcessMessage(message)
				processedAny = true
			end

			-- Process High, Normal with limits
			for priority = 1, 2 do
				local limit = PROCESS_LIMITS[priority]
				local processedCount = 0
				local currentQueue = self.MessageQueues[priority]
				while not currentQueue:IsEmpty() and processedCount < limit do
					local message = currentQueue:Dequeue()

					-- Corrected / Safer Debug Print:
				--	print(os.time(), string.format("[Mgr Proc] Cycle - Prio %d, ProcCount %d, Limit %d. Dequeued item:", priority, processedCount, limit))
				--	print("  ---> Message Content:", message) -- Print the table directly

					self:ProcessMessage(message)
					processedCount += 1
					processedAny = true
				end
			end

			-- Guarantee *some* processing for Low/Background
			local currentTime = tick()
			local timeAvailable = cycleStartTime + CYCLE_WAIT - currentTime
			local guaranteeLower = not processedAny or timeAvailable > MIN_PROCESS_TIME

			if guaranteeLower then
				for priority = 3, 4 do
					local limit = PROCESS_LIMITS[priority]
					local processedCount = 0
					local processTarget = math.max(1, limit) -- Try at least 1
					local currentQueue = self.MessageQueues[priority]
					while not currentQueue:IsEmpty() and processedCount < processTarget do
						local message = currentQueue:Dequeue()

						-- Corrected / Safer Debug Print:
				--		print(os.time(), string.format("[Mgr Proc] Cycle Low/BG - Prio %d, ProcCount %d, Limit %d. Dequeued item:", priority, processedCount, limit))
				--		print("  ---> Message Content:", message) -- Print the table directly

						self:ProcessMessage(message)
						processedCount += 1
					end
				end
			end

	--		 print(os.time(), "[Queue] Cycle End") -- Optional: Keep if needed
			task.wait(CYCLE_WAIT)
		end
		 warn("[Queue] Processing Loop EXITED!") -- Should not happen
	end)
end

function ClientManagerImpl:ProcessMessage(message: QueuedMessage?)
	if not message then return end

	local fn = message.callback
	local args = message.args

	if typeof(fn) == "function" then
		local success, err = pcall(function()
			fn(unpack(args))
		end)

		if not success then
			warn("[NetRay ClientManager] Error processing queued message: " .. tostring(err))
			self.Signals.Error:Fire(err, "QueueProcessing", debug.traceback()) -- Add context and traceback
		end
	else
		warn("[NetRay ClientManager] Invalid callback type in message queue:", typeof(fn))
		self.Signals.Error:Fire("Invalid callback type in message queue", message)
	end
end

-- Gets (and waits briefly for) a specific RemoteEvent instance
function ClientManagerImpl:GetRemoteEvent(eventName: string): RemoteEvent?
	if not self.RemoteFolder then
		warn("[NetRay ClientManager] Cannot get RemoteEvent '"..eventName.."': RemoteFolder not found/initialized.")
		return nil
	end

	-- Use FindFirstChild first for efficiency if it already exists
	local remoteEvent = self.RemoteFolder:FindFirstChild(eventName)
	if remoteEvent and remoteEvent:IsA("RemoteEvent") or remoteEvent:IsA("UnreliableRemoteEvent") then
		return remoteEvent
	elseif remoteEvent then
		warn(("[NetRay ClientManager] Instance '%s' exists but is not a RemoteEvent."):format(eventName))
		return nil -- Don't wait if wrong type exists
	end

	-- If not found, wait briefly
	local success, foundEvent = pcall(function()
		return self.RemoteFolder:WaitForChild(eventName, 5) -- Reduced wait time
	end)

	if success and foundEvent and foundEvent:IsA("RemoteEvent") or foundEvent:IsA("UnreliableRemoteEvent") then
		return foundEvent
	elseif success and foundEvent then
		warn(("[NetRay ClientManager] Found '%s' but it is not a RemoteEvent (Type: %s)."):format(eventName, foundEvent.ClassName))
		return nil
	else
		warn(("[NetRay ClientManager] RemoteEvent '%s' not found within timeout."):format(eventName))
		return nil -- Not found or WaitForChild errored
	end
end

-- Gets (and waits briefly for) a specific RemoteFunction instance
function ClientManagerImpl:GetRemoteFunction(eventName: string): RemoteFunction?
	if not self.RemoteFolder then
		warn("[NetRay ClientManager] Cannot get RemoteFunction '"..eventName.."': RemoteFolder not found/initialized.")
		return nil
	end
	local remoteName = eventName .. "_RF"

	-- Use FindFirstChild first
	local remoteFunction = self.RemoteFolder:FindFirstChild(remoteName)
	if remoteFunction and remoteFunction:IsA("RemoteFunction") then
		return remoteFunction
	elseif remoteFunction then
		warn(("[NetRay ClientManager] Instance '%s' exists but is not a RemoteFunction."):format(remoteName))
		return nil
	end

	-- If not found, wait briefly
	local success, foundFunc = pcall(function()
		return self.RemoteFolder:WaitForChild(remoteName, 5) -- Reduced wait time
	end)

	if success and foundFunc and foundFunc:IsA("RemoteFunction") then
		return foundFunc
	elseif success and foundFunc then
		warn(("[NetRay ClientManager] Found '%s' but it is not a RemoteFunction (Type: %s)."):format(remoteName, foundFunc.ClassName))
		return nil
	else
		warn(("[NetRay ClientManager] RemoteFunction '%s' not found within timeout."):format(remoteName))
		return nil -- Not found or WaitForChild errored
	end
end

function ClientManagerImpl:_InternalRegisterEvent(eventName: string, options: ClientEventOptions?): ClientRegisteredEventInfo?
	options = options or {}
	if self.Events[eventName] then
		-- Return existing info if already registered (e.g., multiple GetEvent calls)
		return self.Events[eventName]
	end

	local remoteEvent = self:GetRemoteEvent(eventName)
	if not remoteEvent then
		warn("[NetRay ClientManager] Failed to find RemoteEvent for internal registration: " .. eventName)
		return nil -- Cannot proceed without the remote
	end

	-- Create the info structure
	local eventInfo: ClientRegisteredEventInfo = {
		Remote = remoteEvent,
		Options = options,
		Handlers = {} -- Handlers added by ClientEvent:OnEvent
	}
	self.Events[eventName] = eventInfo

	-- Set up client-side circuit breaker if specified in options
	if options.circuitBreaker and not self.CircuitBreakers[eventName] then
		local cbOpts = options.circuitBreaker
		self.CircuitBreakers[eventName] = CircuitBreakerModule.new({
			failureThreshold = cbOpts.failureThreshold or 3, -- Lower threshold for client
			resetTimeout = cbOpts.resetTimeout or 15, -- Shorter timeout for client
			fallback = cbOpts.fallback
		}) :: CircuitBreaker
	end

	self.Signals.EventRegistered:Fire(eventName)

	return eventInfo
end

-- Checks if an outgoing request would exceed throttle limits
function ClientManagerImpl:CheckOutgoingThrottle(): boolean
	local now = tick()
	local throttleState = self.OutgoingRequests

	-- Check global limit (reset logic moved to background task)
	if throttleState.Count >= throttleState.Limit then
		self.Signals.ThrottleExceeded:Fire("global", throttleState.Count, throttleState.Limit)
		return false
	end

	-- Check burst limit (reset logic moved to background task)
	if throttleState.Burst.Count >= throttleState.Burst.Limit then
		self.Signals.ThrottleExceeded:Fire("burst", throttleState.Burst.Count, throttleState.Burst.Limit)
		return false
	end

	-- Increment counters *after* checks pass
	throttleState.Count += 1
	throttleState.Burst.Count += 1

	return true -- Allowed
end

-- Enqueues a message (callback + args) for prioritized processing
function ClientManagerImpl:EnqueueMessage(priority: number, callback: (...any) -> (), ...)
	-- Validate priority
	priority = math.clamp(priority or Constants.Priority.NORMAL or 2, 0, 4) -- Use Constants
	local queue = self.MessageQueues[priority]
	if not queue then
		warn("[NetRay ClientManager] Invalid priority level for EnqueueMessage:", priority)
		queue = self.MessageQueues[Constants.Priority.NORMAL or 2] -- Fallback
	end

	local args = {...}
	local message: QueuedMessage = {
		callback = callback,
		args = args
	}
	queue:Enqueue(message)
end

-- Executes client-side middleware for a given event/data
function ClientManagerImpl:InvokeMiddleware(eventName: string, data: any): (boolean, any?)
	return self.Middleware:Execute(eventName, nil, data)
end

-- Return the implementation table
return ClientManagerImpl


--!optimize 2

--[[
    ServerEvent.lua
    Handles server-side event operations, with type validation and optimized networking
    Author: Asta (@TheYusufGamer)
]]

local Players = game:GetService("Players")

-- Type Imports
local ServerManagerModule = require(script.Parent.Parent.Server.ServerManager)
local TypeChecker = require(script.Parent.Parent.Shared.TypeChecker)
local DynamicSender = require(script.Parent.Parent.Shared.DynamicSender)
local Constants = require(script.Parent.Parent.Shared.Constants)

-- Assume TypeChecker exports its result type
type TypeCheckerResult = TypeChecker.TypeCheckerResult -- e.g., {success: boolean, error: string?}
-- Define Player type alias
type Player = Players.Player
-- Import ServerManager type and options type
type ServerManager = ServerManagerModule.ServerManager
type ServerEventOptions = ServerManagerModule.ServerEventOptions -- Import from ServerManager

export type ServerEventType = {
	-- Properties matching NetRayEvent (for compatibility)
	Name: string,
	Options: ServerEventOptions,
	RemoteEvent: RemoteEvent,
	TypeDefinition: any?,
	Priority: number,

	-- Server-Specific Implementation Details
	ServerManager: ServerManager,
	UseCompression: boolean,
	Batchable: boolean,

	-- Methods matching NetRayEvent (Server-side subset + OnEvent)
	-- Note: Using ServerEventType here for 'self' as it's the internal implementation type
	OnEvent: (self: ServerEventType, callback: (player: Player, data: any) -> ()) -> ServerEventType,
	FireClient: (self: ServerEventType, player: Player, data: any) -> (),
	FireAllClients: (self: ServerEventType, data: any) -> (),
	FireAllClientsExcept: (self: ServerEventType, excludedPlayer: Player, data: any) -> (),
	FireFilteredClients: (self: ServerEventType, filter: (player: Player) -> boolean, data: any) -> (),

	-- Internal Helper Methods
	SetupListeners: (self: ServerEventType) -> (),
}

-- Type for the handlers stored in ServerManager
type ServerEventHandler = (player: Player, data: any) -> ()

-- Define the ServerEvent module table
local ServerEvent = {}
ServerEvent.__index = ServerEvent

-- Constructor
function ServerEvent.new(eventName: string, options: ServerEventOptions?, serverManager: ServerManager): ServerEventType
	options = options or {}
	-- Use ServerEventType for self's type annotation here
	local self: ServerEventType = setmetatable({}, ServerEvent)

	self.Name = eventName
	self.Options = options
	self.ServerManager = serverManager -- No cast needed if serverManager param is already typed

	-- Register with the server manager and get event info
	local eventData = serverManager:RegisterEvent(eventName, self.Options)
	self.RemoteEvent = eventData.Remote :: RemoteEvent

	-- Initialize the Handlers array in the ServerManager's storage if it doesn't exist
	if not eventData.Handlers then
		eventData.Handlers = {} :: {ServerEventHandler}
	end

	-- Determine compression need (internal detail)
	self.UseCompression = options.compression == true

	-- Store type definitions
	self.TypeDefinition = options.typeDefinition

	-- Store priority (Matches NetRayEvent)
	self.Priority = options.priority or Constants.PRIORITY.NORMAL or 2 -- Default to NORMAL

	-- Store batching preference (internal detail)
	self.Batchable = options.batchable or false -- Default to false

	-- Set up listeners *after* all properties are initialized
	self:SetupListeners()

	-- Return the created object. Cast ensures it matches the explicit return type.
	return self :: ServerEventType
end

-- Internal method to set up RemoteEvent listeners
function ServerEvent:SetupListeners()
	self.RemoteEvent.OnServerEvent:Connect(function(player: Player, encodedData: any)
		-- Rate Limit Check
		if not self.ServerManager:CheckRateLimit(player, self.Name) then
			warn(("[NetRay ServerEvent] Player %s (%d) rate limited for event %s"):format(player.Name, player.UserId, self.Name))
			return
		end

		local success, decodedData = pcall(function()
			return DynamicSender:DecodeReceivedData(encodedData)
		end)

		if not success then
			local err = decodedData -- pcall returns error message as second arg on failure
			warn(("[NetRay ServerEvent] Failed to decode data for event '%s' from player %s (%d): %s"):format(self.Name, player.Name, player.UserId, tostring(err)))
			self.ServerManager.Signals.Error:Fire("DecodeError", self.Name, player, err)
			return
		end

		local eventInfo = self.ServerManager.Events[self.Name]
		if not eventInfo or not eventInfo.Handlers then
			warn(("[NetRay ServerEvent] Received event '%s' but no handlers registered or event info missing."):format(self.Name))
			return
		end
		local handlers = eventInfo.Handlers :: {ServerEventHandler} -- Assert type

		-- Function to process a single event payload
		local function processSingleEvent(targetPlayer: Player, data: any): boolean -- Added return type annotation
			-- 1. Type Checking
			if self.TypeDefinition then
				local typeCheckResult: TypeCheckerResult = TypeChecker:Validate(data, self.TypeDefinition)
				if not typeCheckResult.success then
					warn(("[NetRay ServerEvent] Type validation failed for event '%s' from player %s (%d): %s"):format(
						self.Name, targetPlayer.Name, targetPlayer.UserId, typeCheckResult.error or "Unknown error"))
					self.ServerManager.Signals.Error:Fire("TypeValidationError", self.Name, targetPlayer, data, typeCheckResult.error)
					return false
				end
			end

			-- 2. Middleware
			local continueProcessing, processedData = self.ServerManager:InvokeMiddleware(self.Name, targetPlayer, data)
			if not continueProcessing then
				if self.ServerManager.DebugEnabled then
					print(("[NetRay ServerEvent] Middleware blocked event '%s' from player %s (%d)"):format(self.Name, targetPlayer.Name, targetPlayer.UserId))
				end
				return false
			end
			local finalData = if processedData ~= nil then processedData else data

			-- 3. Circuit Breaker Check
			local circuitBreaker = self.ServerManager.CircuitBreakers[self.Name]
			if circuitBreaker and not circuitBreaker:IsAllowed() then
				if self.ServerManager.DebugEnabled then
					print(("[NetRay ServerEvent] Circuit breaker tripped for event '%s', blocking request from %s (%d)."):format(self.Name, targetPlayer.Name, targetPlayer.UserId))
				end
				if typeof(circuitBreaker.Options.fallback) == "function" then
					-- Use task.spawn for safety if fallback can error
					task.spawn(circuitBreaker.Options.fallback, targetPlayer, finalData)
				end
				return false
			end

			-- 4. Dispatch to Handlers
			if #handlers == 0 then
				if self.ServerManager.DebugEnabled then
					print(("[NetRay ServerEvent] Event '%s' received from player %s (%d), but no OnEvent handlers are attached."):format(self.Name, targetPlayer.Name, targetPlayer.UserId))
				end
			else
				self.ServerManager.Signals.EventFired:Fire(self.Name, targetPlayer, finalData)
				for _, handler in ipairs(handlers) do
					task.spawn(handler, targetPlayer, finalData)
				end
			end

			-- 5. Record Circuit Breaker Success (only if CB exists)
			if circuitBreaker then
				circuitBreaker:RecordSuccess()
			end
			return true -- Indicate successful processing
		end -- End of processSingleEvent function

		-- Check if we received an array of events from a batch
		if typeof(decodedData) == "table" and #decodedData > 0 and typeof(decodedData[1]) == "table" then
			if self.ServerManager.DebugEnabled then
				print(("[NetRay ServerEvent] Processing %d batched events for '%s' from player %s (%d)"):format(#decodedData, self.Name, player.Name, player.UserId))
			end

			local allSucceeded = true
			for _, eventData in ipairs(decodedData :: {any}) do
				local success = processSingleEvent(player, eventData) -- Process using the original player context
				if not success then
					allSucceeded = false
					-- If one fails, the CB state might change for subsequent ones.
					-- The failure is recorded within processSingleEvent if it wasn't due to an already open CB.
				end
			end
		else
			-- Process as a single event
			local success = processSingleEvent(player, decodedData)
			-- If the single processing failed *and* it wasn't because the CB was open, record failure.
			if not success then
				local circuitBreaker = self.ServerManager.CircuitBreakers[self.Name]
			end
		end
	end)
end

-- Method to register a handler for this event
function ServerEvent:OnEvent(callback: ServerEventHandler): ServerEventType
	if typeof(callback) ~= "function" then
		-- error stops execution, no need to return self after
		error("[NetRay ServerEvent] OnEvent expects a function(player, data) as its argument", 2)
	end

	local eventInfo = self.ServerManager.Events[self.Name]
	if not eventInfo then
		error("[NetRay ServerEvent] Cannot call OnEvent for '"..self.Name.."': Event not properly registered in ServerManager.", 2)
	end
	-- Ensure Handlers array exists (should be guaranteed by .new)
	eventInfo.Handlers = eventInfo.Handlers or {} :: {ServerEventHandler}

	table.insert(eventInfo.Handlers, callback)

	return self -- Return self for chaining
end

-- Method to fire the event to a specific client
function ServerEvent:FireClient(player: Player, data: any)
	if not data then
		self.RemoteEvent:FireClient(player)
	end

	if not player or not player:IsA("Player") or not player:IsDescendantOf(Players) then
		error(("[NetRay ServerEvent] FireClient for '%s' expects a valid Player argument, got %s"):format(self.Name, typeof(player)), 2)
		-- Return immediately after error
		return
	end

	-- 1. Type Checking
	if self.TypeDefinition then
		local typeCheckResult: TypeCheckerResult = TypeChecker:Validate(data, self.TypeDefinition)
		if not typeCheckResult.success then
			error(("[NetRay ServerEvent] Outgoing type validation failed for event '%s': %s"):format(
				self.Name, typeCheckResult.error or "Unknown error"), 2)
			return
		end
	end

	-- 2. Middleware (Outgoing)
	local continueProcessing, processedData = self.ServerManager:InvokeMiddleware(self.Name .. "_Outgoing", player, data)
	if not continueProcessing then
		if self.ServerManager.DebugEnabled then
			print(("[NetRay ServerEvent] Outgoing middleware blocked event '%s' to player %s (%d)"):format(self.Name, player.Name, player.UserId))
		end
		return
	end
	local finalData = if processedData ~= nil then processedData else data

	-- 3. Send using DynamicSender
	local dsOptions = {
		batchable = self.Batchable,
		forceComparison = self.Options.forceComparison
	}

	DynamicSender:Send(self.RemoteEvent, finalData, player, dsOptions)

	-- 4. Fire Signal
	self.ServerManager.Signals.EventFired:Fire(self.Name .. "_SentClient", player, finalData)
end

-- Method to fire the event to all connected clients
function ServerEvent:FireAllClients(data: any)
	-- 1. Type Checking
	if self.TypeDefinition then
		local typeCheckResult: TypeCheckerResult = TypeChecker:Validate(data, self.TypeDefinition)
		if not typeCheckResult.success then
			error(("[NetRay ServerEvent] Outgoing type validation failed for event '%s' (FireAllClients): %s"):format(
				self.Name, typeCheckResult.error or "Unknown error"), 2)
			return
		end
	end

	if not data then
		self.RemoteEvent:FireAllClients(data)
		return
	end

	-- 2. Middleware (Outgoing)
	local continueProcessing, processedData = self.ServerManager:InvokeMiddleware(self.Name .. "_Outgoing", nil, data)
	if not continueProcessing then
		if self.ServerManager.DebugEnabled then
			print(("[NetRay ServerEvent] Outgoing middleware blocked event '%s' (FireAllClients)"):format(self.Name))
		end
		return
	end
	local finalData = if processedData ~= nil then processedData else data

	-- 3. Send using DynamicSender
	local allPlayers = Players:GetPlayers()
	if #allPlayers == 0 then return end

	local dsOptions = {
		batchable = self.Batchable,
		forceComparison = self.Options.forceComparison
	}

	DynamicSender:Send(self.RemoteEvent, finalData, nil)

	-- 4. Fire Signal
	self.ServerManager.Signals.EventFired:Fire(self.Name .. "_SentAll", nil, finalData)

	if self.ServerManager.DebugEnabled then
		print(("[NetRay ServerEvent] Event '%s' fired to %d clients (FireAllClients)"):format(
			self.Name, #allPlayers))
	end
end

-- Method to fire to all clients except one
function ServerEvent:FireAllClientsExcept(excludedPlayer: Player, data: any)
	if not excludedPlayer or not excludedPlayer:IsA("Player") then
		error(("[NetRay ServerEvent] FireAllClientsExcept for '%s' expects a valid Player argument, got %s"):format(self.Name, typeof(excludedPlayer)), 2)
		return
	end

	-- 1. Type Checking
	if self.TypeDefinition then
		local typeCheckResult: TypeCheckerResult = TypeChecker:Validate(data, self.TypeDefinition)
		if not typeCheckResult.success then
			error(("[NetRay ServerEvent] Outgoing type validation failed for event '%s' (FireAllClientsExcept): %s"):format(
				self.Name, typeCheckResult.error or "Unknown error"), 2)
			return
		end
	end

	-- 2. Filter Players
	local playersToSend = {}
	for _, player in ipairs(Players:GetPlayers()) do
		if player ~= excludedPlayer then
			table.insert(playersToSend, player)
		end
	end
	if #playersToSend == 0 then return end

	if not data then
		for _, plr in playersToSend do
			self.RemoteEvent:FireClient(plr)
		end
	end

	-- 3. Middleware (Outgoing)
	local continueProcessing, processedData = self.ServerManager:InvokeMiddleware(self.Name .. "_Outgoing", nil, data)
	if not continueProcessing then
		if self.ServerManager.DebugEnabled then
			print(("[NetRay ServerEvent] Outgoing middleware blocked event '%s' (FireAllClientsExcept)"):format(self.Name))
		end
		return
	end
	local finalData = if processedData ~= nil then processedData else data

	-- 4. Send using DynamicSender
	DynamicSender:SendToMany(self.RemoteEvent, finalData, playersToSend)
	local dsOptions = {
		batchable = self.Batchable,
		forceComparison = self.Options.forceComparison
	}

	-- 5. Fire Signal
	self.ServerManager.Signals.EventFired:Fire(self.Name .. "_SentExcept", excludedPlayer, finalData, dsOptions)

	if self.ServerManager.DebugEnabled then
		print(("[NetRay ServerEvent] Event '%s' fired to %d clients (excluding %s)"):format(
			self.Name, #playersToSend, excludedPlayer.Name))
	end
end

-- Method to fire to clients matching a filter function
function ServerEvent:FireFilteredClients(filter: (player: Player) -> boolean, data: any)
	if typeof(filter) ~= "function" then
		error(("[NetRay ServerEvent] FireFilteredClients for '%s' expects a filter function as the first argument"):format(self.Name), 2)
		return
	end

	-- 1. Type Checking
	if self.TypeDefinition then
		local typeCheckResult: TypeCheckerResult = TypeChecker:Validate(data, self.TypeDefinition)
		if not typeCheckResult.success then
			error(("[NetRay ServerEvent] Outgoing type validation failed for event '%s' (FireFilteredClients): %s"):format(
				self.Name, typeCheckResult.error or "Unknown error"), 2)
			return
		end
	end

	-- 2. Filter Players
	local playersToSend = {}
	-- Use pcall to safely execute the user-provided filter function
	local successFilter, filterResult = pcall(function()
		for _, player in ipairs(Players:GetPlayers()) do
			if filter(player) then
				table.insert(playersToSend, player)
			end
		end
	end)

	if not successFilter then
		-- filterResult contains the error message here
		warn(("[NetRay ServerEvent] Error executing filter function for event '%s': %s"):format(self.Name, tostring(filterResult)))
		self.ServerManager.Signals.Error:Fire("FilterFunctionError", self.Name, filterResult)
		return -- Don't proceed if filter failed
	end

	if #playersToSend == 0 then return end

	if not data then
		for _, plr in playersToSend do
			self.RemoteEvent:FireClient(plr)
		end
	end

	-- 3. Middleware (Outgoing)
	local continueProcessing, processedData = self.ServerManager:InvokeMiddleware(self.Name .. "_Outgoing", nil, data)
	if not continueProcessing then
		if self.ServerManager.DebugEnabled then
			print(("[NetRay ServerEvent] Outgoing middleware blocked event '%s' (FireFilteredClients)"):format(self.Name))
		end
		return
	end
	local finalData = if processedData ~= nil then processedData else data

	local dsOptions = {
		batchable = self.Batchable,
		forceComparison = self.Options.forceComparison
	}

	-- 4. Send using DynamicSender
	DynamicSender:SendToMany(self.RemoteEvent, finalData, playersToSend,dsOptions)

	-- 5. Fire Signal
	self.ServerManager.Signals.EventFired:Fire(self.Name .. "_SentFiltered", nil, finalData)

	if self.ServerManager.DebugEnabled then
		print(("[NetRay ServerEvent] Event '%s' fired to %d clients (filtered)"):format(
			self.Name, #playersToSend))
	end
end

return ServerEvent


--!optimize 2

--[[
    ServerManager.lua
    Manages server-side networking operations, event registration, and security
    Author: Asta (@TheYusufGamer)
]]

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Middleware = require(script.Parent.Parent.Shared.Middleware)
local Queue = require(script.Parent.Parent.Shared.Queue)
local Errors = require(script.Parent.Parent.Shared.Errors)
local CircuitBreaker = require(script.Parent.Parent.Shared.CircuitBreaker)
local Constants = require(script.Parent.Parent.Shared.Constants)
local SignalPlus = require(script.Parent.Parent.ThirdParty.SignalPlus)

-- Type Imports
type Middleware = Middleware.Middleware
type Queue<T> = Queue.Queue<T>
type CircuitBreaker = CircuitBreaker.CircuitBreaker
type SignalPlus = SignalPlus.SignalPlus

type Player = Players.Player

-- Define helper types for configuration and state
type RateLimitConfigOptions = {
	maxRequests: number?,
	timeWindow: number?,
	burstWindow: number?,
	burstLimit: number?
}

type CircuitBreakerConfigOptions = {
	failureThreshold: number?,
	resetTimeout: number?,
	fallback: ((...any) -> (...any))?
}

-- This type represents the options table passed when registering an event
export type ServerEventOptions = {
	rateLimit: RateLimitConfigOptions?,
	circuitBreaker: CircuitBreakerConfigOptions?,
	priority: number?, -- Expected based on queue usage
	typeDefinition: any?, -- Expected based on NetRayEvent type
	[string]: any -- Allow other potential options
}

-- This type represents the actual configuration stored after processing options
type RateLimitConfig = {
	MaxRequests: number,
	TimeWindow: number,
	BurstWindow: number,
	BurstLimit: number
}

-- Type for state stored per player per event for rate limiting
type PlayerEventRateData = {
	WindowStart: number,
	Count: number,
	BurstStart: number,
	BurstCount: number
}

-- Type for the message stored in the queue
type QueuedMessage = {
	callback: (...any) -> (),
	args: {any}
}

-- Type for the options passed to ServerManager.new
type ServerManagerOptions = {
	IncomingLimits: {[number]: number}?, -- UserId -> Limit
	DefaultIncomingLimit: number?,
	DefaultTimeoutDuration: number?
}

-- Type for the ServerManager class itself
export type ServerManager = {
	-- Properties
	RemoteFolder: Folder?, -- Optional until Initialize completes
	Events: {[string]: ServerRegisteredEventInfo}, -- Map eventName to info
	RequestHandlers: {[string]: RequestHandlerInfo}, -- Map requestName to info (define RequestHandlerInfo later)
	RateLimits: {[string]: RateLimitConfig}, -- Map eventName to config
	PlayerRates: {[number]: {[string]: PlayerEventRateData}}, -- Map UserId -> eventName -> data
	Middleware: Middleware,
	MessageQueues: {[number]: Queue<QueuedMessage>}, -- Map priority level to Queue
	CircuitBreakers: {[string]: CircuitBreaker}, -- Map eventName to CircuitBreaker instance
	Signals: ServerManagerSignals,
	IncomingRequests: {
		Limits: {[number]: number}?,
		DefaultLimit: number,
		Counts: {[number]: number},
		ResetTimers: {[number]: number}
	},
	PlayerTimeouts: {
		Timeouts: {[number]: {[string]: number}}, -- Map UserId -> eventName -> timeout timestamp
		DefaultDuration: number
	},
	DebugEnabled: boolean?, -- Added based on usage in SetPlayerTimeout

	-- Methods
	Initialize: (self: ServerManager) -> ServerManager,
	StartQueueProcessing: (self: ServerManager) -> (),
	ProcessMessage: (self: ServerManager, message: QueuedMessage?) -> (),
	CreateRemoteEvent: (self: ServerManager, eventName: string) -> RemoteEvent,
	CreateRemoteFunction: (self: ServerManager, eventName: string) -> RemoteFunction,
	RegisterEvent: (self: ServerManager, eventName: string, options: ServerEventOptions?) -> ServerRegisteredEventInfo,
	CheckRateLimit: (self: ServerManager, player: Player, eventName: string) -> boolean,
	EnqueueMessage: (self: ServerManager, priority: number, callback: (...any) -> (), ...any) -> (),
	InvokeMiddleware: (self: ServerManager, eventName: string, player: Player, data: any) -> (boolean, any?), -- Returns continueProcessing, potentially modifiedData
	CheckPlayerTimeout: (self: ServerManager, eventName: string, userId: number) -> boolean,
	SetPlayerTimeout: (self: ServerManager, eventName: string, userId: number, duration: number?) -> (),
	CheckIncomingThrottle: (self: ServerManager, userId: number) -> boolean,

	-- Constructor (defined separately for clarity)
	new: (options: ServerManagerOptions?) -> ServerManager
}

-- Internal type for storing registered event info
type ServerRegisteredEventInfo = {
	Remote: RemoteEvent,
	Options: ServerEventOptions, -- Store the original options
	Handlers: {[any]: any} -- Placeholder; actual type depends on ServerEvent implementation
}

-- Placeholder for Request Handler Info (needs definition based on RequestServer.lua)
type RequestHandlerInfo = {
	Remote: RemoteFunction,
	Handler: (player: Player, ...any) -> ...any,
	Options: any -- Define RequestOptions later
}

-- Type definition for the Signals table
type ServerManagerSignals = {
	EventRegistered: SignalPlus, -- (eventName: string)
	EventFired: SignalPlus,      -- (eventName: string, player: Player?, data: any) -- Player might be nil for FireAll
	RequestSent: SignalPlus,     -- (requestName: string, player: Player, data: any)
	RequestReceived: SignalPlus, -- (requestName: string, player: Player, data: any)
	RateLimitExceeded: SignalPlus, -- (player: Player, eventName: string, limitType: "window" | "burst")
	CircuitBroken: SignalPlus,   -- (eventName: string)
	CircuitReset: SignalPlus,    -- (eventName: string)
	PlayerJoined: SignalPlus,    -- (player: Player)
	PlayerLeft: SignalPlus,      -- (player: Player)
	Error: SignalPlus,            -- (err: any)
	ThrottleExceeded: SignalPlus -- (throttleType: string, userId: number, duration: number?)
}

-- Constants for processing limits per cycle (already defined, just for context)
local PROCESS_LIMITS = Constants.PROCESS_LIMITS or { -- Use Constants fallback otherwise
	[0] = math.huge, -- Process all Critical
	[1] = 5,         -- High
	[2] = 3,         -- Normal
	[3] = 2,         -- Low
	[4] = 1          -- Background
}
local MIN_PROCESS_TIME = Constants.MIN_PROCESS_TIME or 0.005 -- Min time slice for lower priorities if higher are busy
local CYCLE_WAIT = Constants.CYCLE_WAIT or 0.01

-- ============================================================================
-- ServerManager Implementation
-- ============================================================================

local ServerManagerImpl = {}
ServerManagerImpl.__index = ServerManagerImpl

-- Constructor
function ServerManagerImpl.new(options: ServerManagerOptions?): ServerManager
	options = options or {}

	local self = setmetatable({}, ServerManagerImpl)

	-- Initialize properties with types where possible
	self.RemoteFolder = nil
	self.Events = {} :: {[string]: ServerRegisteredEventInfo}
	self.RequestHandlers = {} :: {[string]: RequestHandlerInfo} -- Cast needed until RequestHandlerInfo is fully defined
	self.RateLimits = {} :: {[string]: RateLimitConfig}
	self.PlayerRates = {} :: {[number]: {[string]: PlayerEventRateData}}
	self.Middleware = Middleware.new() :: Middleware -- Cast if Middleware.new() isn't typed
	self.MessageQueues = { -- Provide type hint for the table keys/values
		[0] = Queue.new(),
		[1] = Queue.new(),
		[2] = Queue.new(),
		[3] = Queue.new(),
		[4] = Queue.new()
	} :: {[number]: Queue<QueuedMessage>}
	self.CircuitBreakers = {} :: {[string]: CircuitBreaker}
	self.Signals = { -- Initialize signals
		EventRegistered = SignalPlus(),
		EventFired = SignalPlus(),
		RequestSent = SignalPlus(),
		RequestReceived = SignalPlus(),
		RateLimitExceeded = SignalPlus(),
		CircuitBroken = SignalPlus(),
		CircuitReset = SignalPlus(),
		PlayerJoined = SignalPlus(),
		PlayerLeft = SignalPlus(),
		Error = SignalPlus(),
		ThrottleExceeded = SignalPlus()
	} :: ServerManagerSignals
	self.IncomingRequests = {
		Limits = options.IncomingLimits,
		DefaultLimit = options.DefaultIncomingLimit or 60,
		Counts = {} :: {[number]: number},
		ResetTimers = {} :: {[number]: number}
	}
	self.PlayerTimeouts = {
		Timeouts = {} :: {[number]: {[string]: number}},
		DefaultDuration = options.DefaultTimeoutDuration or 1
	}
	self.DebugEnabled = false -- Initialize if needed

	-- Connect player events
	-- Consider adding PlayerAdded connection if needed for PlayerRates initialization
	Players.PlayerAdded:Connect(function(player: Player)
		self.PlayerRates[player.UserId] = {} -- Initialize player rate map on join
		self.Signals.PlayerJoined:Fire(player) -- Fire PlayerJoined signal
	end)

	Players.PlayerRemoving:Connect(function(player: Player)
		self.Signals.PlayerLeft:Fire(player)
		if player and player.UserId then
			local userId = player.UserId
			self.PlayerRates[userId] = nil -- Clear rate limit data
			self.IncomingRequests.Counts[userId] = nil -- Clear throttle count
			self.IncomingRequests.ResetTimers[userId] = nil -- Clear throttle timer
			self.PlayerTimeouts.Timeouts[userId] = nil -- Clear timeouts
		end
	end)

	return self :: ServerManager -- Cast the final object to the defined type
end

function ServerManagerImpl:Initialize(): ServerManager
	-- Create or get the remote events folder
	self.RemoteFolder = ReplicatedStorage:FindFirstChild("NetRayRemotes")
	if not self.RemoteFolder then
		self.RemoteFolder = Instance.new("Folder")
		self.RemoteFolder.Name = "NetRayRemotes"
		self.RemoteFolder.Parent = ReplicatedStorage
		assert(self.RemoteFolder:IsA("Folder"), "Failed to create or find RemoteFolder") -- Add assertion
	end

	-- Player rate tracking initialization moved to PlayerAdded connection in .new()

	-- Initialize queue processing
	self:StartQueueProcessing()

	return self
end

function ServerManagerImpl:StartQueueProcessing()
	task.spawn(function()
		while true do
			local cycleStartTime = tick()
			local processedAny = false

			-- Process Critical (all)
			local criticalQueue = self.MessageQueues[0]
			while not criticalQueue:IsEmpty() do
				local message = criticalQueue:Dequeue()
				self:ProcessMessage(message)
				processedAny = true
			end

			-- Process High, Normal with limits
			for priority = 1, 2 do
				local limit = PROCESS_LIMITS[priority]
				local processedCount = 0
				local currentQueue = self.MessageQueues[priority]
				while not currentQueue:IsEmpty() and processedCount < limit do
					local message = currentQueue:Dequeue()
					self:ProcessMessage(message)
					processedCount += 1
					processedAny = true
				end
			end

			-- Guarantee *some* processing for Low/Background
			local currentTime = tick()
			local timeAvailable = cycleStartTime + CYCLE_WAIT - currentTime
			local guaranteeLower = not processedAny or timeAvailable > MIN_PROCESS_TIME

			if guaranteeLower then
				for priority = 3, 4 do
					local limit = PROCESS_LIMITS[priority]
					local processedCount = 0
					local processTarget = math.max(1, limit) -- Ensure at least 1 attempt if queue not empty
					local currentQueue = self.MessageQueues[priority]
					while not currentQueue:IsEmpty() and processedCount < processTarget do
						local message = currentQueue:Dequeue()
						self:ProcessMessage(message)
						processedCount += 1
					end
				end
			end

			task.wait(CYCLE_WAIT)
		end
	end)
end

function ServerManagerImpl:ProcessMessage(message: QueuedMessage?)
	if not message then return end

	local fn = message.callback
	local args = message.args

	-- Check type defensively, although QueuedMessage type ensures it should be a function
	if typeof(fn) == "function" then
		local success, err = pcall(function()
			fn(unpack(args))
		end)

		if not success then
			warn("[NetRay ServerManager] Error processing queued message: " .. tostring(err))
			self.Signals.Error:Fire(err, debug.traceback()) -- Include traceback
		end
	else
		warn("[NetRay ServerManager] Invalid callback type in message queue:", typeof(fn))
		self.Signals.Error:Fire("Invalid callback type in message queue", message)
	end
end

function ServerManagerImpl:CreateRemoteEvent(eventName: string, options: ServerEventOptions?): RemoteEvent
	assert(self.RemoteFolder, "RemoteFolder not initialized")
	local existingRemote = self.RemoteFolder:FindFirstChild(eventName)
	if existingRemote and existingRemote:IsA("RemoteEvent") then
		return existingRemote
	elseif existingRemote then
		warn("[NetRay ServerManager] Instance named", eventName, "exists but is not a RemoteEvent. Destroying and recreating.")
		existingRemote:Destroy()
	end

	local remoteEvent
	if options.UnreliableRemoteEvent == true then
		remoteEvent = Instance.new("UnreliableRemoteEvent")
	else
		remoteEvent = Instance.new("RemoteEvent")
	end
	remoteEvent.Name = eventName
	remoteEvent.Parent = self.RemoteFolder
	return remoteEvent
end

function ServerManagerImpl:CreateRemoteFunction(eventName: string): RemoteFunction
	assert(self.RemoteFolder, "RemoteFolder not initialized")
	local remoteName = eventName .. "_RF"
	local existingRemote = self.RemoteFolder:FindFirstChild(remoteName)
	if existingRemote and existingRemote:IsA("RemoteFunction") then
		return existingRemote
	elseif existingRemote then
		warn("[NetRay ServerManager] Instance named", remoteName, "exists but is not a RemoteFunction. Destroying and recreating.")
		existingRemote:Destroy()
	end

	local remoteFunction = Instance.new("RemoteFunction")
	remoteFunction.Name = remoteName
	remoteFunction.Parent = self.RemoteFolder
	return remoteFunction
end

function ServerManagerImpl:RegisterEvent(eventName: string, options: ServerEventOptions?): ServerRegisteredEventInfo
	options = options or {} -- Ensure options is a table
	if self.Events[eventName] then
		warn("[NetRay ServerManager] Event already registered: " .. eventName .. ". Returning existing registration.")
		return self.Events[eventName]
	end

	local remoteEvent = self:CreateRemoteEvent(eventName, options)
	local eventInfo: ServerRegisteredEventInfo = {
		Remote = remoteEvent,
		Options = options,
		Handlers = {}
	}
	self.Events[eventName] = eventInfo

	-- Set up rate limits for this event if specified
	if options.rateLimit then
		local rl = options.rateLimit
		self.RateLimits[eventName] = {
			MaxRequests = rl.maxRequests or Constants.RATE_LIMIT.DEFAULT_MAX_REQUESTS,
			TimeWindow = rl.timeWindow or Constants.RATE_LIMIT.DEFAULT_TIME_WINDOW,
			BurstWindow = rl.burstWindow or Constants.RATE_LIMIT.DEFAULT_BURST_WINDOW,
			BurstLimit = rl.burstLimit or Constants.RATE_LIMIT.DEFAULT_BURST_LIMIT
		}
	end

	-- Set up circuit breaker if specified
	if options.circuitBreaker then
		local cbOpts = options.circuitBreaker
		self.CircuitBreakers[eventName] = CircuitBreaker.new({
			failureThreshold = cbOpts.failureThreshold or 5,
			resetTimeout = cbOpts.resetTimeout or 30,
			fallback = cbOpts.fallback
		}) :: CircuitBreaker -- Cast if CircuitBreaker.new isn't typed
	end

	self.Signals.EventRegistered:Fire(eventName)

	return eventInfo
end

function ServerManagerImpl:CheckRateLimit(player: Player, eventName: string): boolean
	local rateLimitConfig = self.RateLimits[eventName]
	if not rateLimitConfig then return true end

	local userId = player.UserId
	if not userId then
		warn("[NetRay ServerManager] CheckRateLimit called with invalid player:", player)
		return false
	end

	local playerLimits = self.PlayerRates[userId]
	if not playerLimits then
		playerLimits = {}
		self.PlayerRates[userId] = playerLimits
	end

	local eventLimits = playerLimits[eventName]
	local now = tick()

	if not eventLimits then
		eventLimits = { WindowStart = now, Count = 0, BurstStart = now, BurstCount = 0 }
		playerLimits[eventName] = eventLimits
	end

	-- Check and reset windows
	if now >= eventLimits.WindowStart + rateLimitConfig.TimeWindow then
		eventLimits.WindowStart = now
		eventLimits.Count = 0
	end
	if now >= eventLimits.BurstStart + rateLimitConfig.BurstWindow then
		eventLimits.BurstStart = now
		eventLimits.BurstCount = 0
	end

	-- Check limits
	if eventLimits.BurstCount >= rateLimitConfig.BurstLimit then
		self.Signals.RateLimitExceeded:Fire(player, eventName, "burst")
		return false
	end
	if eventLimits.Count >= rateLimitConfig.MaxRequests then
		self.Signals.RateLimitExceeded:Fire(player, eventName, "window")
		return false
	end

	-- Increment counts
	eventLimits.Count += 1
	eventLimits.BurstCount += 1

	return true
end

function ServerManagerImpl:EnqueueMessage(priority: number, callback: (...any) -> (), ...)
	-- Validate priority
	priority = math.clamp(priority or Constants.PRIORITY.NORMAL or 2, 0, 4) -- Use Constants if available
	local queue = self.MessageQueues[priority]
	if not queue then
		warn("[NetRay ServerManager] Invalid priority level for EnqueueMessage:", priority)
		queue = self.MessageQueues[Constants.PRIORITY.NORMAL or 2] -- Fallback to normal
	end

	local args = {...}
	local message: QueuedMessage = {
		callback = callback,
		args = args
	}
	queue:Enqueue(message)
end

function ServerManagerImpl:InvokeMiddleware(eventName: string, player: Player, data: any): (boolean, any?)
	return self.Middleware:Execute(eventName, player, data)
end

function ServerManagerImpl:CheckPlayerTimeout(eventName: string, userId: number): boolean
	local playerTimeoutMap = self.PlayerTimeouts.Timeouts[userId]
	if not playerTimeoutMap then
		return true -- No timeouts recorded for this player
	end

	local timeoutUntil = playerTimeoutMap[eventName]
	if not timeoutUntil then
		return true -- No timeout for this specific event
	end

	if tick() >= timeoutUntil then
		playerTimeoutMap[eventName] = nil -- Timeout expired
		-- Clean up the playerTimeoutMap if it becomes empty
		 if next(playerTimeoutMap) == nil then
		  self.PlayerTimeouts.Timeouts[userId] = nil
		 end
		return true
	end

	return false -- Still on timeout
end

function ServerManagerImpl:SetPlayerTimeout(eventName: string, userId: number, duration: number?)
	duration = duration or self.PlayerTimeouts.DefaultDuration

	local playerTimeoutMap = self.PlayerTimeouts.Timeouts[userId]
	if not playerTimeoutMap then
		playerTimeoutMap = {}
		self.PlayerTimeouts.Timeouts[userId] = playerTimeoutMap
	end

	local timeoutUntil = tick() + duration
	playerTimeoutMap[eventName] = timeoutUntil

	self.Signals.ThrottleExceeded:Fire(eventName, userId, duration) -- Signal uses eventName as type here

	if self.DebugEnabled then -- Check for DebugEnabled flag
		print(("[NetRay ServerManager] Set timeout for player %d on event %s for %.1f seconds"):format(
			userId, eventName, duration))
	end
end

function ServerManagerImpl:CheckIncomingThrottle(userId: number): boolean
	local now = tick()
	local counts = self.IncomingRequests.Counts
	local timers = self.IncomingRequests.ResetTimers

	if not timers[userId] or now >= timers[userId] then
		counts[userId] = 0
		timers[userId] = now + 60 -- Reset window
	end

	counts[userId] = (counts[userId] or 0) + 1
	local limit = self.IncomingRequests.Limits and self.IncomingRequests.Limits[userId] or self.IncomingRequests.DefaultLimit

	if counts[userId] > limit then
		self.Signals.ThrottleExceeded:Fire("incoming_requests", userId) -- Type is "incoming_requests"
		return false
	end

	return true
end

return ServerManagerImpl


--!optimize 2

--[[
    CircuitBreaker.lua
    Implements the circuit breaker pattern to prevent cascading failures
    Optimized version with adaptive timeouts and improved state tracking
    Author: Asta (@TheYusufGamer)
]]

-- Assuming SignalPlus is typed
local SignalPlusModule = require(script.Parent.Parent.ThirdParty.SignalPlus)
local Constants = require(script.Parent.Constants)
type SignalPlus = SignalPlusModule.SignalPlus

-- Define the module table
local CircuitBreaker = {}
CircuitBreaker.__index = CircuitBreaker

-- Define State Enum Type
local CircuitStateType = {
	CLOSED = "CLOSED",
	OPEN = "OPEN",
	HALF_OPEN = "HALF_OPEN"
}
export type CircuitState = typeof(CircuitStateType.CLOSED) -- "CLOSED" | "OPEN" | "HALF_OPEN"
CircuitBreaker.State = CircuitStateType

-- Type for the options passed to CircuitBreaker.new
type CircuitBreakerOptions = {
	failureThreshold: number?,
	resetTimeout: number?,         -- Base timeout in seconds
	fallback: ((...any) -> any)?,  -- Optional fallback function signature
	halfOpenMaxRequests: number?,
	adaptiveTimeouts: boolean?,
	minimumTimeout: number?,
	maximumTimeout: number?,
	healthCheckInterval: number?   -- Interval in seconds
}

-- Type for the internal metrics structure
type CircuitBreakerMetrics = {
	totalFailures: number,
	totalSuccesses: number,
	lastOpenTime: number,
	openCount: number,
	successRateAfterRecovery: number,
	failureRates: {number}, -- Ring buffer of last N failure rates
	averageRecoveryTime: number,
	-- Calculated fields added by GetMetrics
	currentState: CircuitState?,
	failureCount: number?,
	timeInCurrentState: number?,
	currentTimeout: number?,
	averageFailureRate: number?,
}

-- Type for the Signals table within CircuitBreaker instance
type CircuitBreakerSignals = {
	StateChanged: SignalPlus, -- (oldState: CircuitState, newState: CircuitState)
	FailureRecorded: SignalPlus, -- ()
	SuccessRecorded: SignalPlus, -- ()
	Recovered: SignalPlus -- (recoveryTime: number)
}

-- Type for the CircuitBreaker class itself
export type CircuitBreaker = {
	-- Properties
	Options: { -- Use non-optional types for internal storage after defaulting
		failureThreshold: number,
		resetTimeout: number,
		fallback: ((...any) -> any)?,
		halfOpenMaxRequests: number,
		adaptiveTimeouts: boolean,
		minimumTimeout: number,
		maximumTimeout: number,
		healthCheckInterval: number
	},
	State: CircuitState,
	FailureCount: number,
	LastFailureTime: number,
	LastStateChange: number,
	CurrentTimeout: number, -- Current adaptive timeout value
	ErrorHistory: {number}, -- Timestamps of last N errors
	ConsecutiveSuccesses: number,
	IsHealthCheckScheduled: boolean,
	HalfOpenRequestCount: number,
	Metrics: CircuitBreakerMetrics,
	Signals: CircuitBreakerSignals,

	-- Methods
	IsAllowed: (self: CircuitBreaker) -> boolean,
	Execute: <T>(self: CircuitBreaker, fn: (...any) -> T, ... any) -> T,
	RecordSuccess: (self: CircuitBreaker) -> (),
	RecordFailure: (self: CircuitBreaker) -> (),
	TransitionTo: (self: CircuitBreaker, newState: CircuitState) -> (),
	PerformHealthCheck: (self: CircuitBreaker) -> (),
	ForceState: (self: CircuitBreaker, state: CircuitState) -> (),
	GetMetrics: (self: CircuitBreaker) -> CircuitBreakerMetrics,

	-- Constructor
	new: (options: CircuitBreakerOptions?) -> CircuitBreaker
}

-- Internal helper for adaptive timeout (types parameters and return)
local function calculateAdaptiveTimeout(errorHistory: {number}, baseTimeout: number): number
	if #errorHistory < 2 then
		return baseTimeout
	end

	local totalInterval = 0
	for i = 2, #errorHistory do
		totalInterval += errorHistory[i] - errorHistory[i-1]
	end
	local avgInterval = totalInterval / (#errorHistory - 1)

	if avgInterval < 1 then return baseTimeout * 2
	elseif avgInterval < 5 then return baseTimeout * 1.5
	else return baseTimeout
	end
end

-- Constructor
function CircuitBreaker.new(options: CircuitBreakerOptions?): CircuitBreaker
	options = options or {}
	local self: CircuitBreaker = setmetatable({}, CircuitBreaker)

	-- Initialize Options with defaults and assert types
	self.Options = {
		failureThreshold = options.failureThreshold or Constants.CIRCUIT_BREAKER.DEFAULT_FAILURE_THRESHOLD,
		resetTimeout = options.resetTimeout or Constants.CIRCUIT_BREAKER.DEFAULT_RESET_TIMEOUT,
		fallback = options.fallback, -- Keep as optional function
		halfOpenMaxRequests = options.halfOpenMaxRequests or 1,
		adaptiveTimeouts = options.adaptiveTimeouts ~= false,
		minimumTimeout = options.minimumTimeout or 5,
		maximumTimeout = options.maximumTimeout or 300,
		healthCheckInterval = options.healthCheckInterval or 5
	}

	self.State = CircuitStateType.CLOSED
	self.FailureCount = 0
	self.LastFailureTime = 0
	self.LastStateChange = tick()
	self.CurrentTimeout = self.Options.resetTimeout
	self.ErrorHistory = {} :: {number}
	self.ConsecutiveSuccesses = 0
	self.IsHealthCheckScheduled = false
	self.HalfOpenRequestCount = 0

	self.Metrics = {
		totalFailures = 0, totalSuccesses = 0, lastOpenTime = 0, openCount = 0,
		successRateAfterRecovery = 0, failureRates = {} :: {number}, averageRecoveryTime = 0
	} :: CircuitBreakerMetrics

	self.Signals = {
		StateChanged = SignalPlusModule.new(),
		FailureRecorded = SignalPlusModule.new(),
		SuccessRecorded = SignalPlusModule.new(),
		Recovered = SignalPlusModule.new()
	} :: CircuitBreakerSignals

	return self :: CircuitBreaker
end

-- Determines if a request should be allowed through
function CircuitBreaker:IsAllowed(): boolean
	local now = tick()

	if self.State == CircuitStateType.OPEN then
		if now - self.LastStateChange >= self.CurrentTimeout then
			self:TransitionTo(CircuitStateType.HALF_OPEN)
			-- Allow the first request in HALF_OPEN immediately after transition
			self.HalfOpenRequestCount = 1 -- Increment here as we are allowing it
			return true
		end
		return false -- Still Open
	elseif self.State == CircuitStateType.HALF_OPEN then
		-- Allow up to max requests, increment is done when returning true
		if self.HalfOpenRequestCount < self.Options.halfOpenMaxRequests then
			self.HalfOpenRequestCount += 1
			return true
		end
		return false -- Exceeded HALF_OPEN attempts
	end

	-- CLOSED state always allows
	return true
end

-- Execute a function with circuit breaker protection
-- Use generic <T> for the return type of the wrapped function
function CircuitBreaker:Execute<T>(fn: (...any) -> T, ...): T
	if not self:IsAllowed() then
		-- Circuit is open or HALF_OPEN limit reached
		if self.Options.fallback then
			-- Safely call fallback
			local fbSuccess, fbResult = pcall(self.Options.fallback, ...)
			if fbSuccess then
				return fbResult :: T -- Assume fallback returns compatible type T
			else
				error("Circuit breaker fallback function failed: " .. tostring(fbResult))
			end
		else
			error("Circuit breaker is open - request rejected")
		end
	end

	-- Try executing the protected function
	local args = {...}
	local success, resultOrError = pcall(fn, unpack(args))

	if success then
		self:RecordSuccess()
		return resultOrError :: T -- Assert return type T
	else
		self:RecordFailure()
		-- Failure occurred, use fallback if available
		if self.Options.fallback then
			local fbSuccess, fbResult = pcall(self.Options.fallback, unpack(args)) -- Pass original args to fallback
			if fbSuccess then
				return fbResult :: T -- Assume fallback returns compatible type T
			else
				error("Circuit breaker fallback function failed after primary failure: " .. tostring(fbResult))
			end
		else
			error(resultOrError) -- Re-throw the original error if no fallback
		end
	end
end

-- Record a successful operation
function CircuitBreaker:RecordSuccess()
	self.Metrics.totalSuccesses += 1

	if self.State == CircuitStateType.HALF_OPEN then
		self.ConsecutiveSuccesses += 1
		if self.ConsecutiveSuccesses >= self.Options.halfOpenMaxRequests then
			local recoveryTime = 0
			if self.Metrics.lastOpenTime > 0 then -- Ensure lastOpenTime was set
				recoveryTime = tick() - self.Metrics.lastOpenTime
				if self.Metrics.averageRecoveryTime > 0 then
					self.Metrics.averageRecoveryTime = (self.Metrics.averageRecoveryTime + recoveryTime) / 2
				else
					self.Metrics.averageRecoveryTime = recoveryTime
				end
			end
			self:TransitionTo(CircuitStateType.CLOSED)
			self.Signals.Recovered:Fire(recoveryTime)
		end
	elseif self.State == CircuitStateType.CLOSED then
		-- Reset failure count in CLOSED state only if it was > 0
		if self.FailureCount > 0 then
			self.FailureCount = 0
			self.ErrorHistory = {} :: {number} -- Clear history on recovery
		end
	end

	self.Signals.SuccessRecorded:Fire()
end

-- Record a failed operation
function CircuitBreaker:RecordFailure()
	local now = tick()
	self.LastFailureTime = now
	self.Metrics.totalFailures += 1

	table.insert(self.ErrorHistory, now)
	if #self.ErrorHistory > 10 then -- Limit history size
		table.remove(self.ErrorHistory, 1)
	end

	if self.State == CircuitStateType.HALF_OPEN then
		-- Failure in HALF_OPEN means transition back to OPEN
		if self.Options.adaptiveTimeouts then
			self.CurrentTimeout = math.min(self.CurrentTimeout * 1.5, self.Options.maximumTimeout)
		end
		self:TransitionTo(CircuitStateType.OPEN)
		-- Fire failure signal *after* transition
		self.Signals.FailureRecorded:Fire()
		return
	end

	-- Only increment failure count and check threshold if CLOSED
	if self.State == CircuitStateType.CLOSED then
		self.FailureCount += 1

		-- Calculate failure rate for metrics
		local totalRequests = self.Metrics.totalSuccesses + self.Metrics.totalFailures
		if totalRequests > 0 then
			local failureRate = self.Metrics.totalFailures / totalRequests
			table.insert(self.Metrics.failureRates, failureRate)
			if #self.Metrics.failureRates > 10 then
				table.remove(self.Metrics.failureRates, 1)
			end
		end

		-- Check threshold
		if self.FailureCount >= self.Options.failureThreshold then
			if self.Options.adaptiveTimeouts then
				self.CurrentTimeout = calculateAdaptiveTimeout(self.ErrorHistory, self.Options.resetTimeout)
				self.CurrentTimeout = math.clamp(self.CurrentTimeout, self.Options.minimumTimeout, self.Options.maximumTimeout)
			else
				self.CurrentTimeout = self.Options.resetTimeout
			end
			self:TransitionTo(CircuitStateType.OPEN)
		end
	end

	self.Signals.FailureRecorded:Fire()
end

-- Change the circuit state
function CircuitBreaker:TransitionTo(newState: CircuitState)
	if self.State == newState then return end -- No change

	local oldState = self.State
	self.State = newState
	self.LastStateChange = tick()
	local wasHealthCheckScheduled = self.IsHealthCheckScheduled -- Store before potentially resetting

	if newState == CircuitStateType.CLOSED then
		self.FailureCount = 0
		self.ConsecutiveSuccesses = 0
		self.HalfOpenRequestCount = 0
		self.ErrorHistory = {} :: {number}
		self.IsHealthCheckScheduled = false -- Cancel any pending health check logic
	elseif newState == CircuitStateType.OPEN then
		self.Metrics.openCount += 1
		self.Metrics.lastOpenTime = tick()
		self.HalfOpenRequestCount = 0 -- Reset this for when it transitions back to HALF_OPEN
		-- Schedule health check if enabled and not already running
		if not wasHealthCheckScheduled and self.Options.healthCheckInterval > 0 then
			self.IsHealthCheckScheduled = true
			task.delay(self.Options.healthCheckInterval, function()
				-- Check state again inside delay in case it changed
				if self.IsHealthCheckScheduled then
					self:PerformHealthCheck()
				end
			end)
		end
	elseif newState == CircuitStateType.HALF_OPEN then
		self.ConsecutiveSuccesses = 0
		self.HalfOpenRequestCount = 0 -- Reset before allowing first request
		self.IsHealthCheckScheduled = false -- Don't run health checks in HALF_OPEN
	end

	self.Signals.StateChanged:Fire(oldState, newState)
end

-- Perform a periodic health check (internal use)
function CircuitBreaker:PerformHealthCheck()
	-- Clear scheduled flag regardless of outcome
	self.IsHealthCheckScheduled = false

	if self.State ~= CircuitStateType.OPEN then return end -- Only run if still OPEN

	local now = tick()
	if now - self.LastStateChange >= self.CurrentTimeout / 2 then
		if self.State == CircuitStateType.OPEN then -- Double check state before transition
			self:TransitionTo(CircuitStateType.HALF_OPEN)
		end
	else
		-- Reschedule next check if still within timeout window
		if self.Options.healthCheckInterval > 0 then
			self.IsHealthCheckScheduled = true
			task.delay(self.Options.healthCheckInterval, function()
				if self.IsHealthCheckScheduled then
					self:PerformHealthCheck()
				end
			end)
		end
	end
end

-- Manually force the circuit to a specific state
function CircuitBreaker:ForceState(state: CircuitState)
	-- Check if the provided state string is a valid key in the enum table
	if CircuitStateType[state] then -- Assumes state is "CLOSED", "OPEN", etc.
		self:TransitionTo(state)
	else
		error("[NetRay CircuitBreaker] Invalid circuit state provided to ForceState: " .. tostring(state), 2)
	end
end

-- Get current metrics and status
function CircuitBreaker:GetMetrics(): CircuitBreakerMetrics
	local now = tick()
	local metrics: CircuitBreakerMetrics = table.clone(self.Metrics)

	metrics.currentState = self.State
	metrics.failureCount = self.FailureCount
	metrics.timeInCurrentState = now - self.LastStateChange
	metrics.currentTimeout = self.CurrentTimeout

	local numRates = #self.Metrics.failureRates
	if numRates > 0 then
		local sum = 0
		for _, rate in ipairs(self.Metrics.failureRates) do sum += rate end
		metrics.averageFailureRate = sum / numRates
	else
		metrics.averageFailureRate = 0
	end

	return metrics
end

return CircuitBreaker :: { State: typeof(CircuitStateType), new: (options: CircuitBreakerOptions?) -> CircuitBreaker }


--!native
--!optimize 2

--[[
    LZ4_Lib

    Pure Luau implementation of LZ4 Block Compression Format (Luau Compliant)
    Includes BinaryEncoder integration and header handling.
]]

local LZ4_Lib = {}

--[[* Dependencies *]]
local BinaryEncoderModule = require(script.Parent.Parent.ThirdParty.BinaryEncoder)
type BinaryEncoder = {
	encode: (data: any) -> string?, -- Returns string on success, nil on error
	decode: (encoded: string) -> any? -- Returns original data on success, nil on error
}

-- Assert dependency exists and has expected methods
assert(BinaryEncoderModule, "LZ4_Lib requires the BinaryEncoder module.")
assert(typeof(BinaryEncoderModule.encode) == "function", "BinaryEncoder module must have an 'encode' function.")
assert(typeof(BinaryEncoderModule.decode) == "function", "BinaryEncoder module must have a 'decode' function.")

-- Assign typed module
local BinaryEncoder: BinaryEncoder = BinaryEncoderModule

--[[* Type Definitions *]]
-- Type for the LZ4_Lib module itself
export type LZ4Module = {
	Compress: (self: LZ4Module, data: any) -> string?, -- Returns header+payload string or nil
	Decompress: (self: LZ4Module, data: string) -> any? -- Returns original data or nil
}

--[[* Constants *]]
-- Header Bytes (Using string constants)
local RAW_STRING_HEADER: string     = string.char(0x01)
local RAW_ENCODED_HEADER: string    = string.char(0x02)
local LZ4_STRING_HEADER: string     = string.char(0x03)
local LZ4_ENCODED_HEADER: string    = string.char(0x04)

-- LZ4 Constants (number types assumed)
local MINMATCH = 4; local KB = 1024; local MB = 1024 * KB; local MAX_INPUT_SIZE = 50 * MB
local HASH_LOG = 12; local HASH_TABLE_SIZE = bit32.lshift(1, HASH_LOG)
local MAX_DISTANCE = bit32.lshift(1, 16) - 1; local ML_BITS = 4
local ML_MASK = bit32.lshift(1, ML_BITS) - 1; local RUN_BITS = 8 - ML_BITS
local RUN_MASK = bit32.lshift(1, RUN_BITS) - 1; local HASH_SEED_32 = 2654435761
local MIN_COMPRESSION_INPUT_LEN = 64; local COMPRESSION_WORTH_RATIO = 0.98

-- Character cache table (type { [number]: string })
local CHARS: {[number]: string} = {}
for i = 0, 255 do CHARS[i] = string.char(i) end

--[[* Helper Functions (LZ4 Core & General) - Typed *]]
local function read_u16_le(input: string, pos: number): (number?, number?) -- Returns value?, newPos?
	local b1 = string.byte(input, pos); local b2 = string.byte(input, pos + 1)
	if not b1 or not b2 then return nil, nil end
	return bit32.bor(b1, bit32.lshift(b2, 8)), pos + 2
end
local function write_u16_le(value: number, chunks: {string})
	table.insert(chunks, CHARS[bit32.band(value, 0xFF)])
	table.insert(chunks, CHARS[bit32.band(bit32.rshift(value, 8), 0xFF)])
end
local function write_lz4_len(len_to_encode: number, baseValue: number, chunks: {string})
	local extra_len = len_to_encode - baseValue
	if extra_len < 0 then extra_len = 0 end
	local val = extra_len
	while val >= 255 do table.insert(chunks, CHARS[0xFF]); val -= 255 end
	table.insert(chunks, CHARS[val])
end
local function read_length(input: string, pos: number, baseLength: number, maskValue: number): (number?, number?) -- Returns length?, newPos?
	local length = baseLength
	if length == maskValue then
		local byteVal; local sum = 0
		repeat
			byteVal = string.byte(input, pos)
			if not byteVal then return nil, pos end -- Check for end of string
			sum += byteVal; pos += 1
		until byteVal ~= 255
		length += sum
	end
	return length, pos
end
local function mul32(a: number, b: number): number -- Internal bit manipulation, returns number
	local aL=bit32.band(a,0xFFFF); local aH=bit32.rshift(a,16); local bL=bit32.band(b,0xFFFF); local bH=bit32.rshift(b,16)
	local p1=aL*bL; local p2=aL*bH; local p3=aH*bL; local rL=bit32.band(p1,0xFFFF); local cy=bit32.rshift(p1,16)
	local mS=cy+bit32.band(p2,0xFFFF)+bit32.band(p3,0xFFFF); local rH=bit32.band(mS,0xFFFF)
	return bit32.bor(bit32.lshift(rH,16),rL)
end
local function hash_sequence(input: string, pos: number): number -- Returns hash number
	local b1=string.byte(input,pos) or 0; local b2=string.byte(input,pos+1) or 0; local b3=string.byte(input,pos+2) or 0; local b4=string.byte(input,pos+3) or 0
	local sequence=bit32.bor(b1,bit32.lshift(b2,8),bit32.lshift(b3,16),bit32.lshift(b4,24))
	local h=mul32(sequence,HASH_SEED_32); return bit32.rshift(h,32-HASH_LOG)
end

--[[* Core LZ4 Compression Logic - Typed *]]
-- Takes string, returns (success: boolean, result: string | nil) - result is compressed string or nil on critical failure
local function compressor(input: string): (boolean, string?)
	local inputLen = #input
	if inputLen == 0 then return true, "" end
	if inputLen > MAX_INPUT_SIZE then warn("LZ4 Input too large"); return false, nil end -- Return nil on failure

	local success, result = pcall(function(): string -- Inner function returns compressed string
		local hashTable: {[number]: number} = {} -- hash -> position
		local outputChunks: {string} = {}
		local ip = 1; local anchor = 1; local inputByte = string.byte

		while ip <= inputLen do
			local ref; local matchFound = false
			if ip <= inputLen - MINMATCH + 1 then
				local h = hash_sequence(input, ip); ref = hashTable[h]; hashTable[h] = ip
				if ref and (ip - ref <= MAX_DISTANCE) then
					-- Check 4 bytes match (manually for potential slight speedup vs substring compare)
					if inputByte(input, ref) == inputByte(input, ip) and inputByte(input, ref+1) == inputByte(input, ip+1) and
						inputByte(input, ref+2) == inputByte(input, ip+2) and inputByte(input, ref+3) == inputByte(input, ip+3) then
						local literalLength = ip - anchor; local offset = ip - ref; local matchLen = MINMATCH
						local ipFast = ip + matchLen; local refFast = ref + matchLen
						-- Find full match length
						while ipFast <= inputLen and inputByte(input, ipFast) == inputByte(input, refFast) do matchLen += 1; ipFast += 1; refFast += 1 end
						-- Write token, lengths, literals, offset
						local tokenLitLen = math.min(literalLength, RUN_MASK); local tokenMatchLen = math.min(matchLen - MINMATCH, ML_MASK)
						local token = bit32.bor(bit32.lshift(tokenLitLen, ML_BITS), tokenMatchLen)
						table.insert(outputChunks, CHARS[token])
						if tokenLitLen == RUN_MASK then write_lz4_len(literalLength, RUN_MASK, outputChunks) end
						if literalLength > 0 then table.insert(outputChunks, string.sub(input, anchor, ip - 1)) end
						write_u16_le(offset, outputChunks)
						if tokenMatchLen == ML_MASK then write_lz4_len(matchLen - MINMATCH, ML_MASK, outputChunks) end
						-- Update pointers
						ip += matchLen; anchor = ip; matchFound = true
					end
				end
			end
			if not matchFound then ip += 1 end -- Advance if no match found
		end
		-- Write final literals
		local finalLiteralLength = inputLen - anchor + 1
		if finalLiteralLength > 0 then
			local tokenLiteralLenPart = math.min(finalLiteralLength, RUN_MASK)
			local token = bit32.lshift(tokenLiteralLenPart, ML_BITS)
			table.insert(outputChunks, CHARS[token])
			if tokenLiteralLenPart == RUN_MASK then write_lz4_len(finalLiteralLength, RUN_MASK, outputChunks) end
			table.insert(outputChunks, string.sub(input, anchor, inputLen))
		end
		return table.concat(outputChunks)
	end)

	if success then return true, result :: string? -- result is string or nil if input was empty initially (handled above)
	else warn("LZ4 Compression Internal Error:", result); return false, nil -- Return nil on internal pcall error
	end
end

--[[* Core LZ4 Decompression Logic - Typed *]]
-- Takes compressed string, returns (success: boolean, result: string | nil) - result is decompressed string or nil on failure
local function decompressor(input: string): (boolean, string?)
	local inputLen = #input
	if inputLen == 0 then return true, "" end

	local success, result = pcall(function(): string -- Inner function returns decompressed string
		local outputBytes: {string} = {}
		local op = 0; local ip = 1; local inputByte = string.byte; local sub = string.sub

		while ip <= inputLen do
			local token = inputByte(input, ip); if not token then error("LZ4 stream end reading token @ "..ip, 0) end; ip += 1
			local literalLengthBase = bit32.rshift(token, ML_BITS)
			local literalLength, newIpLl = read_length(input, ip, literalLengthBase, RUN_MASK); if not literalLength then error("LZ4 stream truncated literal len @ "..ip, 0) end; ip = newIpLl
			if literalLength > 0 then
				local litEnd = ip + literalLength - 1; if litEnd > inputLen then error("LZ4 stream literal OOB", 0) end
				table.insert(outputBytes, sub(input, ip, litEnd)); op += literalLength; ip += literalLength
			end
			if ip > inputLen then break end -- End of stream after literals
			local offset, newIpOff = read_u16_le(input, ip); if not offset then error("LZ4 stream truncated offset @ "..ip, 0) end; if offset == 0 then error("LZ4 stream offset=0 @ "..ip, 0) end; ip = newIpOff
			local matchLengthBase = bit32.band(token, ML_MASK)
			local matchLength, newIpMl = read_length(input, ip, matchLengthBase, ML_MASK); if not matchLength then error("LZ4 stream truncated match len @ "..ip, 0) end; ip = newIpMl
			matchLength += MINMATCH
			local matchSourceStart = op - offset + 1; if matchSourceStart < 1 then error("LZ4 stream invalid offset "..offset.." (op="..op..") @ "..ip, 0) end

			-- Copy match data (Handling overlap)
			local currentOutputStr = table.concat(outputBytes) -- Snapshot current output
			if offset >= matchLength then -- No overlap
				if matchSourceStart + matchLength - 1 > op then error("LZ4 non-overlap OOB", 0) end
				table.insert(outputBytes, sub(currentOutputStr, matchSourceStart, matchSourceStart + matchLength - 1))
			else -- Overlap
				local tempCopyTable = {}; local snapshotLen = #currentOutputStr
				if matchSourceStart > snapshotLen then error("LZ4 overlap source start OOB", 0) end
				for i=1, matchLength do
					local readPos = matchSourceStart + i - 1
					local byteValue: number?
					if readPos <= snapshotLen then byteValue = inputByte(currentOutputStr, readPos)
					else local tempIdx = readPos - snapshotLen; if tempIdx <= 0 or tempIdx > #tempCopyTable then error("LZ4 overlap temp read OOB", 0) end; byteValue = string.byte(tempCopyTable[tempIdx], 1) end
					if not byteValue then error("LZ4 overlap read byte fail", 0) end
					table.insert(tempCopyTable, CHARS[byteValue])
				end
				table.insert(outputBytes, table.concat(tempCopyTable))
			end
			op += matchLength
		end
		-- Return final concatenated string
		return table.concat(outputBytes)
	end)

	if success then return true, result :: string? -- Result is string or nil if empty
	else warn("LZ4 Decompression Internal Error:", result); return false, nil -- Return nil on internal pcall error
	end
end

--[[* Public Interface Methods - Typed *]]

function LZ4_Lib:Compress(data: any): string?
	local payloadData: string
	local isRawString: boolean

	if typeof(data) == "string" then payloadData = data; isRawString = true
	else
		local encodeSuccess, encoded = pcall(BinaryEncoder.encode, data)
		if not encodeSuccess or typeof(encoded) ~= "string" then warn("LZ4 Compress: Encode fail - ", encoded); return nil end
		payloadData = encoded; isRawString = false
	end

	local payloadLen = #payloadData
	if payloadLen < MIN_COMPRESSION_INPUT_LEN then
		return (if isRawString then RAW_STRING_HEADER else RAW_ENCODED_HEADER) .. payloadData
	end

	local compressSuccess, compressedData = compressor(payloadData)
	if not compressSuccess then -- compressor returns nil on failure now
		warn("LZ4 Compress: Compressor fail - sending raw. Size:", payloadLen)
		return (if isRawString then RAW_STRING_HEADER else RAW_ENCODED_HEADER) .. payloadData
	end

	-- Check if compression worthwhile (compressedData is guaranteed string here)
	local compressedStr = compressedData :: string
	if #compressedStr < (payloadLen * COMPRESSION_WORTH_RATIO) then
		return (if isRawString then LZ4_STRING_HEADER else LZ4_ENCODED_HEADER) .. compressedStr
	else -- Not worthwhile
		return (if isRawString then RAW_STRING_HEADER else RAW_ENCODED_HEADER) .. payloadData
	end
end

function LZ4_Lib:Decompress(data: string): any?
	if typeof(data) ~= "string" or #data < 1 then warn("LZ4 Decompress: Invalid input."); return nil end

	local header: string = string.sub(data, 1, 1)
	local payload: string = string.sub(data, 2)
	local needsDecode = false; local isCompressed = false

	if header == RAW_STRING_HEADER then needsDecode=false; isCompressed=false
	elseif header == RAW_ENCODED_HEADER then needsDecode=true; isCompressed=false
	elseif header == LZ4_STRING_HEADER then needsDecode=false; isCompressed=true
	elseif header == LZ4_ENCODED_HEADER then needsDecode=true; isCompressed=true
	else warn("LZ4 Decompress: Unknown header:", string.byte(header,1)); return nil
	end

	local processedPayload: string?
	if isCompressed then
		local decompressSuccess, decompressedData = decompressor(payload)
		if not decompressSuccess then warn("LZ4 Decompress: Decompressor fail. Header:", string.byte(header,1)); return nil end
		processedPayload = decompressedData
	else
		processedPayload = payload
	end

	if processedPayload == nil then warn("LZ4 Decompress: Payload nil post-process. Header:", string.byte(header,1)); return nil end

	if needsDecode then
		local decodeSuccess, originalData = pcall(BinaryEncoder.decode, processedPayload)
		if not decodeSuccess then warn("LZ4 Decompress: Decode fail -", originalData, "- Header:", string.byte(header,1)); return nil end
		return originalData -- Return decoded Luau data
	else
		return processedPayload -- Return raw/decompressed string
	end
end

-- Return the module table, asserting its type
return LZ4_Lib :: LZ4Module


--!optimize 2

--[[
    Constants.lua
    Defines shared constants used throughout the NetRay library
    Author: Asta (@TheYusufGamer)
]]

-- Type definition for the Priority sub-table
type PriorityConstants = {
	CRITICAL: number,
	HIGH: number,
	NORMAL: number,
	LOW: number,
	BACKGROUND: number,
	[string]: number? -- Allows other string keys, value is number or nil
}

-- Type definition for Rate Limit sub-table
type RateLimitConstants = {
	DEFAULT_MAX_REQUESTS: number,
	DEFAULT_TIME_WINDOW: number,
	DEFAULT_BURST_LIMIT: number,
	DEFAULT_BURST_WINDOW: number
}

-- Type definition for Compression sub-table
type CompressionConstants = {
	DEFAULT_THRESHOLD: number,
	COMPRESSION_LEVEL: number
}

-- Type definition for Timeout sub-table
type TimeoutConstants = {
	DEFAULT_REQUEST_TIMEOUT: number,
	CONNECT_TIMEOUT: number,
	QUEUE_PROCESS_INTERVAL: number
}

-- Type definition for Circuit Breaker sub-table
type CircuitBreakerConstants = {
	DEFAULT_FAILURE_THRESHOLD: number,
	DEFAULT_RESET_TIMEOUT: number
}

-- Type definition for Remote Naming sub-table
type RemoteNamingConstants = {
	FUNCTION_SUFFIX: string,
	EVENTS_FOLDER: string
}

-- Define the type for the returned module table
export type ConstantsModule = {
	PRIORITY: PriorityConstants, -- Use corrected type
	RATE_LIMIT: RateLimitConstants,
	COMPRESSION: CompressionConstants,
	TIMEOUT: TimeoutConstants,
	CIRCUIT_BREAKER: CircuitBreakerConstants,
	REMOTE_NAMING: RemoteNamingConstants,
	VERSION: string
}

-- Define the actual constant values, asserting the overall type
local Constants: ConstantsModule = {
	PRIORITY = {
		CRITICAL = 0, HIGH = 1, NORMAL = 2, LOW = 3, BACKGROUND = 4
	},
	PROCESS_LIMITS = {
		[0] = math.huge,
		[1] = 5,
		[2] = 3,
		[3] = 2,
		[4] = 1
	},
	MIN_PROCESS_TIME = 0.005,
	CYCLE_WAIT = 0.1,
	RATE_LIMIT = {
		DEFAULT_MAX_REQUESTS = 120, DEFAULT_TIME_WINDOW = 1,
		DEFAULT_BURST_LIMIT = 20, DEFAULT_BURST_WINDOW = 0.1
	},
	COMPRESSION = {
		DEFAULT_THRESHOLD = 256,
		COMPRESSION_LEVEL = 9,
	},
	TIMEOUT = {
		DEFAULT_REQUEST_TIMEOUT = 10, CONNECT_TIMEOUT = 5, QUEUE_PROCESS_INTERVAL = 0.01
	},
	CIRCUIT_BREAKER = {
		DEFAULT_FAILURE_THRESHOLD = 5, DEFAULT_RESET_TIMEOUT = 30
	},
	REMOTE_NAMING = {
		FUNCTION_SUFFIX = "_RF", EVENTS_FOLDER = "NetRayRemotes"
	},
	VERSION = "1.1.2"
}

return Constants