local JSTranspiler = require(script.JSTranspiler)
local StdLib = require(script:WaitForChild("StdLib"))
local JSEngine = {}

JSEngine.StdLib = StdLib
JSEngine.GuiRegistry = {}
JSEngine.variables = {}
JSEngine.forms = {}
JSEngine.timeouts = {}
JSEngine.intervals = {}
JSEngine.eventListeners = {}
JSEngine.elements = {}

-- Configuration for timeout protection
JSEngine.Config = {
	MaxExecutionTime = 0.5, -- Maximum execution time in seconds
	YieldInterval = 100,    -- Yield after processing this many operations
	DebugMode = false       -- Enable debug logging
}

-- Execution state tracking
local operationCount = 0
local lastYieldTime = os.clock()

-- Function to yield if needed to prevent timeouts
local function yieldIfNeeded()
	operationCount = operationCount + 1

	if operationCount >= JSEngine.Config.YieldInterval then
		operationCount = 0

		local currentTime = os.clock()
		local elapsed = currentTime - lastYieldTime

		if elapsed > JSEngine.Config.MaxExecutionTime then
			if JSEngine.Config.DebugMode then
				print("JS Engine yielding after " .. elapsed .. " seconds")
			end
			task.wait()
			lastYieldTime = os.clock()
		end
	end
end

-- Expose the yieldIfNeeded function to be used by JSTranspiler
JSEngine.yieldIfNeeded = yieldIfNeeded

-- Store a reference to the current document (main GUI container)
local currentDocument = nil

-- Create a document object for DOM operations
JSEngine.document = {}

function JSEngine.document.getElementById(id)
	return JSEngine.getGui(id)
end

function JSEngine.document.querySelector(selector)
	return JSEngine.querySelector(selector)
end

function JSEngine.document.querySelectorAll(selector)
	return JSEngine.querySelectorAll(selector)
end

function JSEngine.document.createElement(tagName)
	return StdLib.DOM.createElement(tagName)
end

JSEngine.document.body = {
	appendChild = function(element)
		-- This is just a stub since we can't actually append to the body in Roblox
		print("Attempted to append to document.body:", element)
	end
}

function JSEngine.setDocument(document)
	currentDocument = document

	-- Set up document.body reference
	if currentDocument then
		JSEngine.document.body = currentDocument
	end
end

function JSEngine.register(id, gui)
	if id then
		JSEngine.GuiRegistry[id] = gui

		-- If this is a form element, register it with the form system
		if gui:GetAttribute("HtmlTag") == "form" then
			JSEngine.forms[id] = {
				elements = {},
				validated = false
			}
		elseif gui:IsA("TextBox") and gui:GetAttribute("FormId") then
			local formId = gui:GetAttribute("FormId")
			if not JSEngine.forms[formId] then
				JSEngine.forms[formId] = {
					elements = {},
					validated = false
				}
			end
			JSEngine.forms[formId].elements[id] = gui
		end
	end
end

function JSEngine.getGui(id)
	if not currentDocument then
		warn("No document set for JSEngine")
		return nil
	end

	-- First look for elements with ID attribute
	local elements = currentDocument:GetDescendants()
	for _, element in ipairs(elements) do
		yieldIfNeeded() -- Prevent timeout during long searches
		if element:GetAttribute("ID") == id then
			return element
		end
	end

	-- Then look for elements with Name matching the ID
	return currentDocument:FindFirstChild(id, true)
end

function JSEngine.execute(jsCode)
	-- Reset operation counters before execution
	operationCount = 0
	lastYieldTime = os.clock()

	if JSEngine.Config.DebugMode then
		print("Executing JavaScript: " .. string.sub(jsCode, 1, 50) .. (string.len(jsCode) > 50 and "..." or ""))
	end

	return JSTranspiler.executeDirectly(jsCode, JSEngine)
end

function JSEngine.evaluateExpression(expr)
	yieldIfNeeded() -- Prevent timeout during complex expressions

	if expr:match('^[\"].*\"$') or expr:match("^'.*'$") then
		return expr:gsub("[\"']", "")
	end
	local n = tonumber(expr)
	if n then return n end
	if JSEngine.variables[expr] then return JSEngine.variables[expr] end
	return expr
end

function JSEngine.setInnerText(id, content)
	local gui = JSEngine.getGui(id)
	if gui and (gui:IsA("TextLabel") or gui:IsA("TextButton")) then
		gui.Text = JSEngine.evaluateExpression(content)
	end
end

function JSEngine.setInnerHTML(id, content)
	return JSEngine.setInnerText(id, content)
end

function JSEngine.setValue(id, content)
	local gui = JSEngine.getGui(id)
	if gui and gui:IsA("TextBox") then
		gui.Text = JSEngine.evaluateExpression(content)
	end
end

function JSEngine.navigateTo(href)
	warn("[JS] *navigating* to: " .. tostring(href))
end

function JSEngine.historyBack()
	warn("[JS] history.back()  closing GUI")
	local playerGui = game.Players.LocalPlayer:FindFirstChild("PlayerGui")
	if playerGui then
		local htmlGui = playerGui:FindFirstChild("HTMLRender")
		if htmlGui then htmlGui:Destroy() end
	end
end

-- Form validation functions
function JSEngine.validateForm()
	-- Basic form validation
	local isValid = true

	if currentDocument then
		local formElements = currentDocument:GetDescendants()
		for _, element in ipairs(formElements) do
			if element:GetAttribute("required") and element:IsA("TextBox") and element.Text == "" then
				isValid = false

				-- Highlight the invalid field
				local originalColor = element.BackgroundColor3
				element.BackgroundColor3 = Color3.fromRGB(255, 200, 200)

				task.delay(1, function()
					element.BackgroundColor3 = originalColor
				end)
			end
		end
	end

	return isValid
end

-- DOM API functions
local selectorCache = {}

function JSEngine.querySelector(selector)
	if not currentDocument then
		warn("No document set for JSEngine")
		return nil
	end

	-- Check cache first
	local cacheKey = "qs_" .. selector
	if selectorCache[cacheKey] then
		-- Verify the element still exists in the document
		local cached = selectorCache[cacheKey]
		if cached and cached:IsDescendantOf(currentDocument) then
			return cached
		else
			-- Remove invalid cache entry
			selectorCache[cacheKey] = nil
		end
	end

	-- Handle ID selectors (#id)
	if selector:sub(1, 1) == "#" then
		local id = selector:sub(2)
		local result = JSEngine.getGui(id)
		if result then
			selectorCache[cacheKey] = result
		end
		return result
	end

	-- Handle class selectors (.class)
	if selector:sub(1, 1) == "." then
		local className = selector:sub(2)
		local elements = currentDocument:GetDescendants()
		for _, element in ipairs(elements) do
			yieldIfNeeded() -- Prevent timeout during long searches
			local elementClass = element:GetAttribute("Class")
			if elementClass and string.find(elementClass, className) then
				selectorCache[cacheKey] = element
				return element
			end
		end
	end

	-- Handle tag selectors (e.g., "div")
	local elements = currentDocument:GetDescendants()
	for _, element in ipairs(elements) do
		yieldIfNeeded() -- Prevent timeout during long searches
		local tag = element:GetAttribute("HtmlTag")
		if tag and tag:lower() == selector:lower() then
			selectorCache[cacheKey] = element
			return element
		end
	end

	return nil
end

function JSEngine.querySelectorAll(selector)
	if not currentDocument then
		warn("No document set for JSEngine")
		return {}
	end

	-- Check cache first
	local cacheKey = "qsa_" .. selector
	if selectorCache[cacheKey] then
		-- Verify the elements still exist in the document
		local cached = selectorCache[cacheKey]
		local allValid = true

		for i, element in ipairs(cached) do
			if not element:IsDescendantOf(currentDocument) then
				allValid = false
				break
			end
		end

		if allValid then
			return cached
		else
			-- Remove invalid cache entry
			selectorCache[cacheKey] = nil
		end
	end

	local results = {}

	-- Handle ID selectors (#id)
	if selector:sub(1, 1) == "#" then
		local id = selector:sub(2)
		local element = JSEngine.getGui(id)
		if element then
			table.insert(results, element)
		end
		selectorCache[cacheKey] = results
		return results
	end

	-- Handle class selectors (.class)
	if selector:sub(1, 1) == "." then
		local className = selector:sub(2)
		local elements = currentDocument:GetDescendants()
		for _, element in ipairs(elements) do
			yieldIfNeeded() -- Prevent timeout during long searches
			local elementClass = element:GetAttribute("Class")
			if elementClass and string.find(elementClass, className) then
				table.insert(results, element)
			end
		end
		selectorCache[cacheKey] = results
		return results
	end

	-- Handle tag selectors (e.g., "div")
	local elements = currentDocument:GetDescendants()
	for _, element in ipairs(elements) do
		yieldIfNeeded() -- Prevent timeout during long searches
		local tag = element:GetAttribute("HtmlTag")
		if tag and tag:lower() == selector:lower() then
			table.insert(results, element)
		end
	end

	selectorCache[cacheKey] = results
	return results
end

-- Add a function to clear the selector cache when needed
function JSEngine.clearSelectorCache()
	selectorCache = {}
end

-- Define the cleanup function
function JSEngine.cleanup()
	-- Clean up all event listeners
	for element, events in pairs(JSEngine.eventListeners) do
		for eventType, connections in pairs(events) do
			for _, connection in ipairs(connections) do
				connection:Disconnect()
			end
		end
	end

	-- Clean up all timeouts and intervals
	for _, timeout in pairs(JSEngine.timeouts) do
		if timeout.connection then
			timeout.connection:Disconnect()
		end
	end

	for _, interval in pairs(JSEngine.intervals) do
		if interval.connection then
			interval.connection:Disconnect()
		end
	end

	-- Reset all variables
	JSEngine.variables = {}
	JSEngine.timeouts = {}
	JSEngine.intervals = {}
	JSEngine.eventListeners = {}
	JSEngine.elements = {}
	currentDocument = nil

	-- Clear the selector cache
	JSEngine.clearSelectorCache()
end

-- Add event listener support
function JSEngine.addEventListener(element, eventType, handler)
	if not element then
		warn("Cannot add event listener to nil element")
		return
	end

	local connection = nil

	if eventType == "click" then
		-- Find the clickable part of the element
		local button = nil
		if element:IsA("TextButton") or element:IsA("ImageButton") then
			button = element
		else
			button = element:FindFirstChildWhichIsA("TextButton") or element:FindFirstChildWhichIsA("ImageButton")
		end

		if button then
			connection = button.MouseButton1Click:Connect(function()
				pcall(handler, {target = element})
			end)
		end
	elseif eventType == "change" then
		-- For input elements
		if element:GetAttribute("Type") == "checkbox" or element:GetAttribute("Type") == "radio" then
			local button = element:FindFirstChildWhichIsA("TextButton")
			if button then
				connection = button.MouseButton1Click:Connect(function()
					pcall(handler, {target = element})
				end)
			end
		elseif element:IsA("TextBox") then
			connection = element.FocusLost:Connect(function()
				pcall(handler, {target = element})
			end)
		end
	end

	if connection then
		if not JSEngine.eventListeners[element] then
			JSEngine.eventListeners[element] = {}
		end

		if not JSEngine.eventListeners[element][eventType] then
			JSEngine.eventListeners[element][eventType] = {}
		end

		table.insert(JSEngine.eventListeners[element][eventType], connection)
	end
end

return JSEngine


-----------------------------------------------------------
-- Variables
-----------------------------------------------------------
local TagDefinitions = {}

local defs = {
    ["!--"]       = { ignore = true },
    ["#comment"]  = { ignore = true },

    head   = { ignore = true },
    meta   = { void = true,  ignore = true },
    link   = { void = true,  ignore = true },
    script = { ignore = true },
    style  = { ignore = true },
    noscript = { ignore = true },

    area    = { void = true },
    base    = { void = true },
    br      = { void = true },
    col     = { void = true },
    command = { void = true },
    embed   = { void = true },
    hr      = { void = true },
    img     = { void = true },
    input   = { void = true },
    keygen  = { void = true },
    param   = { void = true },
    source  = { void = true },
    track   = { void = true },
    wbr     = { void = true },
    main    = {},
    slot    = { void = true },

    h1 = { component = "p" },
    h2 = { component = "p" },
    h3 = { component = "p" },
    h4 = { component = "p" },
    h5 = { component = "p" },
    h6 = { component = "p" },

    b  = { component = "span", defaults = { Font = Enum.Font.SourceSansBold } },
    strong = { component = "span", defaults = { Font = Enum.Font.SourceSansBold } },
    i  = { component = "span", defaults = { Font = Enum.Font.SourceSansItalic } },
    em = { component = "span", defaults = { Font = Enum.Font.SourceSansItalic } },
    nobr = { component = "span" },

    form = {},
    fieldset = {},
    legend = {},
    label = {},
    button = {},
    select = {},
    option = {},
    optgroup = {},
    textarea = {},

    html = {}, body = {}, div = {}, span = {}, p = {}, a = {},
    ul = {}, ol = {}, li = {}, table = {}, tr = {}, td = {}, th = {},
    thead = {}, tbody = {}, tfoot = {}, caption = {},
    section = {}, article = {}, header = {}, footer = {}, nav = {}, aside = {},

    figure = {},
    figcaption = {},
    details = {},
    summary = {},
    mark = {},
    small = {},
    del = {},
    ins = {},
    sub = {},
    sup = {},

    svg = {},
    g = {},
}

-----------------------------------------------------------
-- Functions
-----------------------------------------------------------
TagDefinitions.definitions = defs

function TagDefinitions.getDefinition(tag: string)
    return defs[string.lower(tag)]
end

function TagDefinitions.getRegistryTag(tag: string): string
    local info = defs[string.lower(tag)]
    if info and type(info.component) == "string" then
        return info.component
    end
    return string.lower(tag)
end

function TagDefinitions.isVoid(tag: string): boolean
    local info = defs[string.lower(tag)]
    return info ~= nil and info.void == true
end

function TagDefinitions.shouldIgnore(tag: string): boolean
    local info = defs[string.lower(tag)]
    return info ~= nil and info.ignore == true
end

function TagDefinitions.getVoidElementSet()
    local set = {}
    for tag, info in pairs(defs) do
        if type(info) == "table" and info.void == true then
            set[tag] = true
        end
    end
    return set
end

return TagDefinitions


-- thegalaxydev

--[=[
	@class Color3

	The Color3 data type describes a color using red, green, and blue components in the range of 0 to 1.
]=]
local Color3 = {}
Color3.__index = Color3

export type Color3 = typeof(Color3) & {
	R: number,
	G: number,
	B: number,
}

--[=[
	Creates a new Color3.
	@param r number
	@param g number
	@param b number
	@return Color3
]=]
function Color3.new(r: number, g: number, b: number): Color3
	local self = setmetatable({}, Color3)

	self.R = math.clamp(r, 0, 1)
	self.G = math.clamp(g, 0, 1)
	self.B = math.clamp(b, 0, 1)

	return self
end

--[=[
	Creates a new Color3 from RGB values.
	@param r number
	@param g number
	@param b number
	@return Color3
]=]
function Color3.fromRGB(r: number, g: number, b: number): Color3
	return Color3.new(r / 255, g / 255, b / 255)
end

--[=[
	Creates a new Color3 from a hex string.
	@param hex string
	@return Color3
]=]
function Color3.fromHex(hex: string): Color3
	local r, g, b = hex:match("(%x%x)(%x%x)(%x%x)")

	r = tonumber(r, 16) or 0
	g = tonumber(g, 16) or 0
	b = tonumber(b, 16) or 0

	return Color3.new(r / 255, g / 255, b / 255)
end

--[=[
	Creates a new Color3 from HSV values.
	@param h number
	@param s number
	@param v number
	@return Color3
]=]
function Color3.fromHSV(h: number, s: number, v: number): Color3
	h = h / 255
	s = s / 255
	v = v / 255

	local r, g, b

	local i = math.floor(h * 6)
	local f = h * 6 - i
	local p = v * (1 - s)
	local q = v * (1 - f * s)
	local t = v * (1 - (1 - f) * s)

	i = i % 6

	if i == 0 then
		r, g, b = v, t, p
	elseif i == 1 then
		r, g, b = q, v, p
	elseif i == 2 then
		r, g, b = p, v, t
	elseif i == 3 then
		r, g, b = p, q, v
	elseif i == 4 then
		r, g, b = t, p, v
	elseif i == 5 then
		r, g, b = v, p, q
	end

	return Color3.new(r, g, b)
end

--[=[
	Converts the Color3 to a hex string.
	@return string
]=]
function Color3:ToHex(): string
	return ("#%02X%02X%02X"):format(self.R * 255, self.G * 255, self.B * 255)
end

--[=[
	Converts the Color3 to HSV values.
	@return number, number, number
]=]
function Color3:ToHSV(): (number, number, number)
	local r, g, b = self.R, self.G, self.B

	local min_val = math.min(r, g, b)
	local max_val = math.max(r, g, b)
	local delta = max_val - min_val

	local h, s, v = 0, 0, max_val

	if max_val ~= 0 then
		s = delta / max_val
	else
		s = 0
		h = -1
		return h, s, v
	end

	if delta == 0 then
		h = 0 --
	elseif max_val == r then
		h = (g - b) / delta % 6
	elseif max_val == g then
		h = (b - r) / delta + 2
	elseif max_val == b then
		h = (r - g) / delta + 4
	end

	h = h / 6

	if h < 0 then
		h = h + 1
	end

	return h, s, v
end

--[=[
	Lerps between two colors.
	@param other Color3
	@param alpha number
	@return Color3
]=]
function Color3:Lerp(other: Color3, alpha: number): Color3
	return Color3.new(
		self.R + (other.R - self.R) * alpha,
		self.G + (other.G - self.G) * alpha,
		self.B + (other.B - self.B) * alpha
	)
end

--[=[
	Converts the Color3 to an integer.
	@return number
]=]
function Color3:ToInt(): number
	return (self.R * 255 * 256 * 256) + (self.G * 255 * 256) + self.B * 255
end

return Color3


--[=[
	@class DiscordWebSocket
	Class to spawn sharded websockets for Discord.
]=]
local DiscordWebSocket = {}
DiscordWebSocket.__index = DiscordWebSocket
local Event = require("./Event")
local Bot = require("../Lib/Bot")
local Task = require("../Util/Task")
local Client = require("../Lib/Client")
local Misc = require("../Util/Misc")
local Timer = require("./Timer")
local Network = require("../Lib/Network")

type NetSocket = Network.WebSocket

type Payload = {op: number?, d: {[string]:any}, s: number?, t: string?}
export type DiscordWebSocket = {
	OnMessage: Event.Event<string>,
	OnClose: Event.Event<number, string>,
	Closed: boolean
} & typeof(DiscordWebSocket)

--[=[
	Creates a new DiscordWebSocket.
	@within DiscordWebSocket
	@param url string
	@param token string
	@param shard_id number
	@param shards number
	@return DiscordWebSocket
]=]
function DiscordWebSocket.new(url: string, token: string, shard_id: number, shards: number) : DiscordWebSocket
	local self = setmetatable({}, DiscordWebSocket)

	self._url = url
	self._socket = Network.WebSocket(url)
	self._last_payload = nil
	self.token = token
	self.shard_id = shard_id
	self.shards = shards

	self.Closed = false

	self.OnMessage = Event.new()
	self.OnClose = Event.new()

	Bot.Shards[shard_id] = {Socket = self, ResumeURL = "", SessionID = ""}

	local connect
	connect = function()
		local retry = false
		self.Closed = false
		local heartbeat_ack = true

		while (not self._socket.getCloseCode() and not self.Closed) or retry do
			retry = false

			local success, message = xpcall(function()
				return self._socket.Next()
			end, function(err)
				print("[WebSocket] Error retrieving next message: " .. err)
				self._socket.Close(4000)
				self.Closed = true
			end)

			if not success then
				print("[WebSocket] Error retrieving next message: " .. message)
				Task.Wait()
				continue
			end

			if not message then
				print("[WebSocket] No message received.")

				print(`[Shard {self.shard_id}] 🔴 WebSocket closed.`, self._socket.getCloseCode())
				pcall(function()
					self._socket.Close(4000)
				end)
				Task.Wait()
				self._socket = Network.WebSocket(Bot.Shards[self.shard_id].ResumeURL)
				self.OnClose:Fire(self._socket.getCloseCode(), Bot.CLOSE_REASONS[self._socket.getCloseCode()] or "Unknown")
				break
			end

			self.OnMessage:Fire(message)

			local s, payload = pcall(function()
				return Network.JsonDecode(message)
			end)

			if not s then
				print(`[Shard {self.shard_id}] 🔴 Error decoding message: {payload}`)
				Task.Wait()
				continue
			end

			if payload.op == 10 then
				local initial_delay = (payload.d.heartbeat_interval * math.random()) / 1000
				self._last_heartbeat_interval = payload.d.heartbeat_interval

				local function heartbeat_end_callback()
					if self._socket.getCloseCode() then
						print(`[Shard {self.shard_id}] 🔴 WebSocket closed.`)
						return
					end

					if not heartbeat_ack then
						print(`[Shard {self.shard_id}] 🧟 Heartbeat not acknowledged. Reconnecting...`)
						self.Closed = true
						return
					end

					local heartbeat_payload = Bot.OP.HEARTBEAT(self._last_payload.s)
					local success, err = pcall(function(...)
						if self._socket.getCloseCode() then
							return
						end

						self:Send(heartbeat_payload)
					end)

					if not success or self._socket.getCloseCode() then
						print(`[Shard {self.shard_id}] 🔴 Heartbeat failed to send: {err}`)
						pcall(function()
							self._socket.Close(4000)
						end)
						self._socket = Network.WebSocket(self._url)
						self.Closed = true
						retry = true
						return
					end

					if self.heartbeat_task then
						if coroutine.status(self.heartbeat_task) == "suspended" then
							Task.Cancel(self.heartbeat_task)
						end
					end

					self.heartbeat_task = Task.Delay(payload.d.heartbeat_interval / 1000, heartbeat_end_callback)

					heartbeat_ack = false
				end

				Task.Delay(initial_delay, function()
					heartbeat_end_callback()
					print(`[Shard {self.shard_id}] 💖 Heartbeat started.`)
				end)

				self:Send(Bot.OP.IDENTIFY(self.token, self.shard_id, self.shards, initial_delay))
				print(`[Shard {self.shard_id}] 🔑 Identify sent.`)
			end

			if payload.op == 7 then
				print(`[Shard {self.shard_id}] 🔄 Reconnect signal received. Attempting to resume.`, payload)
				retry = true
				self._socket.Close(4000)
				self.Closed = true
				self._socket = Network.WebSocket(Bot.Shards[self.shard_id].ResumeURL)
				self:Send(Bot.OP.RESUME(self.token, self._last_payload.s, self.shard_id))
			end

			if payload.op == 9 then
				print(`[Shard {self.shard_id}] 🚫 Invalid session. Reconnecting.`, payload)
				self._socket.Close(4000)
				self._socket = Network.WebSocket(self._url)
				self.Closed = true
				break
			end

			if payload.op == 11 then
				print(`[Shard {self.shard_id}] 💜 Heartbeat received. Uptime: {Misc.FormatTime(os.time() - Bot.STARTED_AT)}`)
				heartbeat_ack = true
			end

			if payload.op == 0 and payload.t then
				Client.Fire(payload.t, payload, self.shard_id)
			end

			self._last_payload = payload

			Task.Wait()

			if retry or self._socket.getCloseCode() then
				print(`[Shard {self.shard_id}] 🔄 Reconnecting WebSocket.`)
				self.Closed = true
				self._socket.Close(4000)
				Task.Wait(1)
				self._socket = Network.WebSocket(self._url)
				heartbeat_ack = true
				self._last_payload = nil
			end
		end

		self.Closed = true
		print(`[Shard {self.shard_id}] 🔴 WebSocket closed.`, self._socket.getCloseCode())
		self._socket.Close(4000)
		self._socket = Network.WebSocket(Bot.Shards[self.shard_id].ResumeURL)
		self.OnClose:Fire(self._socket.getCloseCode(), Bot.CLOSE_REASONS[self._socket.getCloseCode()] or "Unknown")
	end

	self.OnClose:Connect(function(code, reason)
		self.Closed = false
		Task.Defer(connect)
	end)

	if self.heartbeat_task then
		Task.Cancel(self.heartbeat_task)
	end

	connect()

	local proxy = setmetatable({}, {
		__newindex = function(_, index, value)
			if index:sub(1,1):lower() == index:sub(1,1) then
				return
			end

			self[index] = value
		end,

		__index = function(_, index)
			if index:sub(1,1) == "_" then
				return
			end

			return self[index]
		end
	})

	return proxy
end

--[=[
	Sends a payload to Discord.
	@param data Payload
	@within DiscordWebSocket
]=]
function DiscordWebSocket:Send(data: Payload) : ()
	print("[WebSocket] Sending payload: ", data)
	self._socket.Send(Network.JsonEncode(data))
end
return DiscordWebSocket


--[=[
	@class Embed
	Class to create Discord embeds.
]=]
local Embed = {}
local Color3 = require("Color3")
type Color3 = Color3.Color3

local DiscordTypes = require("../Lib/DiscordTypes")

export type Embed = {
	setTitle: (title: string) -> Embed,
	setDescription: (description: string) -> Embed,
	setUrl: (url: string) -> Embed,
	setTimestamp: () -> Embed,
	setColor: (color: Color3) -> Embed,
	setFooter: (text: string, icon_url: string?) -> Embed,
	setImage: (url: string) -> Embed,
	setThumbnail: (url: string) -> Embed,
	setVideo: (url: string) -> Embed,
	setProvider: (name: string, url: string?) -> Embed,
	setAuthor: (name: string, icon_url: string?, url: string?) -> Embed,
	setFields: (fields: { { name: string, value: string, inline: boolean? } }) -> Embed,
	addField: (name: string, value: string, inline: boolean?) -> Embed,
	finish: () -> DiscordTypes.EmbedData,
}

--[=[
	@function new
	@within Embed
	Creates a new Embed.
	@return Embed
]=]
function Embed.new(): Embed
	local newEmbed = {}
	newEmbed._structure = {}

	--[=[
	@function setTitle
	Sets the title of the embed.
	@within Embed
	@param title string
	@return Embed
]=]
	newEmbed.setTitle = function(title: string)
		newEmbed._structure.title = title

		return newEmbed
	end

	--[=[
	@function setDescription
	Sets the description of the embed.
	@within Embed
	@param description string
	@return Embed
	]=]
	newEmbed.setDescription = function(description: string)
		newEmbed._structure.description = description

		return newEmbed
	end

	--[=[
	@function setUrl
	Sets the URL of the embed.
	@within Embed
	@param url string
	@return Embed
	]=]
	newEmbed.setUrl = function(url: string)
		newEmbed._structure.url = url

		return newEmbed
	end

	--[=[
	@function setTimestamp
	Sets the timestamp of the embed.
	@within Embed
	@param time string?
	@return Embed
	]=]
	newEmbed.setTimestamp = function(time: string?)
		local now = time or os.time()
		local tz_offset_sec = os.difftime(now, os.time(os.date("!*t", now)))
		local tz_offset = string.format("%+.2d:%.2d", math.floor(tz_offset_sec / 3600), math.abs(tz_offset_sec % 3600) / 60)

		newEmbed._structure.timestamp = os.date("!%Y-%m-%dT%H:%M:%S") .. tz_offset

		return newEmbed
	end

	--[=[
	@function setColor
	Sets the color of the embed.
	@within Embed
	@param color Color3
	@return Embed
	]=]
	newEmbed.setColor = function(color: Color3)
		newEmbed._structure.color = color:ToInt()

		return newEmbed
	end

	--[=[
	@function setFooter
	Sets the footer of the embed.
	@within Embed
	@param text string
	@param icon_url string?
	@param proxy_icon_url string?
	@return Embed
	]=]
	newEmbed.setFooter = function(text: string, icon_url: string?, proxy_icon_url: string?)
		newEmbed._structure.footer = {
			text = text,
			icon_url = icon_url,
			proxy_icon_url = proxy_icon_url
		}

		return newEmbed
	end

	--[=[
	@function setImage
	Sets the image of the embed.
	@within Embed
	@param url string
	@param height number?
	@param width number?
	@param proxy_url string?
	@return Embed
	]=]
	newEmbed.setImage = function(url: string, height: number?, width: number?, proxy_url: string?)
		newEmbed._structure.image = {
			url = url,
			height = height,
			width = width,
			proxy_url = proxy_url
		}

		return newEmbed
	end

	--[=[
	@function setThumbnail
	Sets the thumbnail of the embed.
	@within Embed
	@param url string
	@param height number?
	@param width number?
	@param proxy_url string?
	@return Embed
	]=]
	newEmbed.setThumbnail = function(url: string, height: number?, width: number?, proxy_url: string?)
		newEmbed._structure.thumbnail = {
			url = url,
			height = height,
			width = width,
			proxy_url = proxy_url
		}

		return newEmbed
	end

	--[=[
	@function setVideo
	Sets the video of the embed.
	@within Embed
	@param url string
	@param height number?
	@param width number?
	@param proxy_url string?
	@return Embed
	]=]
	newEmbed.setVideo = function(url: string, height: number?, width: number?, proxy_url: string?)
		newEmbed._structure.video = {
			url = url,
			height = height,
			width = width,
			proxy_url = proxy_url
		}

		return newEmbed
	end

	--[=[
	@function setProvider
	Sets the provider of the embed.
	@within Embed
	@param name string?
	@param url string?
	@return Embed
	]=]
	newEmbed.setProvider = function(name: string?, url: string?)
		newEmbed._structure.provider = {
			name = name,
			url = url
		}

		return newEmbed
	end

	--[=[
	@function setAuthor
	Sets the author of the embed.
	@within Embed
	@param name string
	@param icon_url string?
	@param url string?
	@param proxy_icon_url string?
	@return Embed
	]=]
	newEmbed.setAuthor = function(name: string, icon_url: string?, url: string?, proxy_icon_url: string?)
		newEmbed._structure.author = {
			name = name,
			icon_url = icon_url,
			url = url,
			proxy_icon_url = proxy_icon_url
		}

		return newEmbed
	end

	--[=[
	@function setFields
	Sets the fields of the embed.
	@within Embed
	@param fields { { name: string, value: string, inline: boolean? } }
	@return Embed
	]=]
	newEmbed.setFields = function(fields: { { name: string, value: string, inline: boolean? } })
		newEmbed._structure.fields = fields

		return newEmbed
	end

	--[=[
	@function addField
	@within Embed
	@param name string
	@param value string
	@param inline boolean?
	@return Embed
	Adds a field to the embed.
	]=]
	newEmbed.addField = function(name: string, value: string, inline: boolean?)
		if not newEmbed._structure.fields then
			newEmbed._structure.fields = {}
		end

		table.insert(newEmbed._structure.fields, {
			name = name,
			value = value,
			inline = inline
		})

		return newEmbed
	end

	--[=[
	@function finish
	Finishes the embed and returns the embed data as a structured table.
	@within Embed
	@return DiscordTypes.EmbedData
	]=]
	newEmbed.finish = function() : DiscordTypes.EmbedData
		return newEmbed._structure
	end

	return newEmbed
end

return Embed


--[=[
	@class Event
	Class to create events.
]=]

local Event = {}
Event.__index = Event
Event.__class = "Event"

export type Event<T...> = {
	Callbacks : {[(T...)->any]: any};
	Waiting : {thread};
	Connect : (Event<T...>, func : (T...)->any) -> Connection,
	Fire : (Event<T...>, T...) -> nil,
	Wait : (Event<T...>, number?) -> T...
}
export type Connection = {
	Connected : boolean,
	Disconnect: () -> nil
}

--[=[
	Creates a new Event.
	@within Event
	@return Event
]=]
function Event.new<T...>() : Event<T...>
	local self = setmetatable({}, Event)
	self.Callbacks = {}
	self.Waiting = {}

	return self
end

--[=[
	Connects a function to the event.
	@within Event
	@param func (any)->any
	@return Connection?
]=]
function Event:Connect<T...>(func: (T...)->any) : Connection?
	local connection = {}
	connection.Connected = true

	function connection.Disconnect(cself)
		self.Callbacks[func] = nil
		cself.Connected = false
	end

	self.Callbacks[func] = newproxy()

	return connection
end

--[=[
	Fires the event.
	@within Event
	@param ... any
]=]
function Event:Fire<T...>(...: T...)
	for func in pairs(self.Callbacks) do
		coroutine.wrap(func)(...)
	end

	local waitingThreads = self.Waiting
    self.Waiting = {}
    for _, thread in ipairs(waitingThreads) do
        coroutine.resume(thread, ...)
    end
end

--[=[
	Waits for the event to fire.
	@within Event
	@param timeout number?
	@return any
]=]
function Event:Wait(timeout)
    local thread = coroutine.running()
    table.insert(self.Waiting, thread)

    if timeout then
        coroutine.wrap(function()
            local startTime = os.clock()
            while os.clock() - startTime < timeout do
                coroutine.yield()
            end
            for i, t in ipairs(self.Waiting) do
                if t == thread then
                    table.remove(self.Waiting, i)
                    coroutine.resume(thread)
                    break
                end
            end
        end)()
    end

    return coroutine.yield()
end

return Event


--[=[
@class LinkedList
A linked list implementation.
]=]
local LinkedList = {}
LinkedList.__index = function(self, key)
	if type(key) == "number" then
		local current = self.Head
		for _ = 1, key - 1 do
			if not current then return nil end
			current = current.Next
		end
		return current
	end
	return LinkedList[key]
end
LinkedList.__len = function(self)
	return self.Length
end

LinkedList.__tostring = function(self)
	local t = {}
	for i,v in self:iter() do
		t[i] = v
	end

	return `LinkedList<{self.Length}> \{{table.concat(t, ", ")}\}`
end

--[=[
@within LinkedList
@type LinkedListNode<T> {Next: LinkedListNode<T>?, Prev: LinkedListNode<T>?, Index: number, Value: T}
A node in the linked list.
]=]
type LinkedListNode<T> = {
	Next: LinkedListNode<T>?,
	Prev: LinkedListNode<T>?,
	Index: number,
	Value: T
}

--[=[
@within LinkedList
@type LinkedList<T> {Head: LinkedListNode<T>?, Tail: LinkedListNode<T>?, Length: number}
A linked list.
]=]
export type LinkedList<T> = {
	Head: LinkedListNode<T>?,
	Tail: LinkedListNode<T>?,
	Length: number,
	PopLeft: (self: LinkedList<T>) -> T?,
	PopRight: (self: LinkedList<T>) -> T?,
	PushLeft: (self: LinkedList<T>, Value: T) -> (),
	PushRight: (self: LinkedList<T>, Value: T) -> (),
	iter: (self: LinkedList<T>) -> (() -> (number?, T?)),
	Remove: (self: LinkedList<T>, value: T) -> boolean,
	RemoveIndex: (self: LinkedList<T>, index: number) -> boolean
}

--[=[
@within LinkedList
@ignore
@param value T
@param index number
@return LinkedListNode<T>
Creates a node for the linked list.
]=]
local function toNode<T>(value: T, index: number): LinkedListNode<T>
	return {
		Next = nil,
		Prev = nil,
		Value = value,
		Index = index
	}
end

--[=[
@within LinkedList
@param ... T
@return LinkedList<T>
Creates a new linked list.
]=]
function LinkedList.new<T>(...: T): LinkedList<T>
	local self = setmetatable({}, LinkedList)
	local elements = {...}

	for i, value in ipairs(elements) do
		elements[i] = toNode(value, i)
	end

	self.Length = #elements
	self.Head = elements[1]
	self.Tail = elements[self.Length]

	if self.Length < 2 then
		return self
	end

	for i = 1, self.Length do
		local current = elements[i]
		current.Next = elements[i + 1]
		current.Prev = elements[i - 1]
	end

	return self
end

--[=[
@within LinkedList
@param Value T
Removes a value from the linked list.
]=]
function LinkedList:Remove<T>(Value: T): boolean
	local current = self.Head

	while current do
		if current.Value == Value then
			if current.Prev then
				current.Prev.Next = current.Next
			else
				self.Head = current.Next
			end

			if current.Next then
				current.Next.Prev = current.Prev
			else
				self.Tail = current.Prev
			end

			self.Length = self.Length - 1

			return true
		end

		current = current.Next
	end

	return false
end

--[=[
@within LinkedList
@param Index number
Removes a value from the linked list by index.
]=]
function LinkedList:RemoveIndex<T>(Index: number): boolean
	if Index < 1 or Index > self.Length then
		return false
	end

	local current = self.Head
	local count = 1

	while current and count < Index do
		current = current.Next
		count += 1
	end

	if current then
		if current.Prev then
			current.Prev.Next = current.Next
		else
			self.Head = current.Next
		end

		if current.Next then
			current.Next.Prev = current.Prev
		else
			self.Tail = current.Prev
		end

		self.Length = self.Length - 1

		local node = current.Next
		while node do
			node.Index -= 1
			node = node.Next
		end

		return true
	end

	return false
end

--[=[
@within LinkedList
An iterator for the linked list, allowing for `for` loops.
]=]
function LinkedList.iter<T>(self: LinkedList<T>)
	local current = self.Head
	return function()
		if current then
			local node = current
			current = current.Next
			return node.Index, node.Value
		end
		return nil
	end
end

--[=[
@within LinkedList
@param Value T
Pushes a value to the right of the linked list.
]=]
function LinkedList:PushRight<T>(Value: T)
	local node = {
		Next = nil,
		Prev = self.Tail,
		Value = Value,
		Index = self.Length + 1
	}

	if self.Tail then
		self.Tail.Next = node
	else
		self.Head = node
	end

	self.Tail = node
	self.Length += 1
end

--[=[
@within LinkedList
@param Value T
Pushes a value to the left of the linked list.
]=]
function LinkedList:PushLeft<T>(Value: T)
	local node = {
		Next = self.Head,
		Prev = nil,
		Value = Value,
		Index = 1
	}

	if self.Head then
		local current = self.Head
		while current do
			current.Index += 1
			current = current.Next
		end
		self.Head.Prev = node
	else
		self.Tail = node
	end

	self.Head = node
	self.Length += 1
end

--[=[
@within LinkedList
@return T?
Pops a value from the right of the linked list.
]=]
function LinkedList:PopRight<T>(): T?
	if not self.Tail then
		return nil
	end

	local Value = self.Tail.Value
	self.Tail = self.Tail.Prev

	if self.Tail then
		self.Tail.Next = nil
	else
		self.Head = nil
	end

	self.Length -= 1
	return Value
end

--[=[
@within LinkedList
@return T?
Pops a value from the left of the linked list.
]=]
function LinkedList:PopLeft<T>(): T?
	if not self.Head then
		return nil
	end

	local Value = self.Head.Value
	self.Head = self.Head.Next

	if self.Head then
		local current = self.Head
		while current do
			current.Index -= 1
			current = current.Next
		end
		self.Head.Prev = nil
	else
		self.Tail = nil
	end

	self.Length -= 1
	return Value
end

return LinkedList


--[=[
	@class Snowflake
	A utility class for decoding and encoding Discord snowflakes.
]=]

local Snowflake = {}

export type Snowflake = {
	id: string,
	timestamp: string,
	workerId: string,
	processId: string,
	increment: string,
}

local bit64 = require('Bit64')

--[=[
	@private
	@param id string -- The snowflake ID
	@return Snowflake -- The decoded snowflake
	@within Snowflake
]=]
local function decodeSnowflake(id: string)
	local discordEpoch = 1420070400000

	local snowflake = bit64.from_string(id)
	local timestamp = bit64.add(bit64.rshift(snowflake, 22), bit64.from_u32(discordEpoch))
	local workerId = bit64.band(bit64.rshift(snowflake, 17), bit64.from_u32(0x1F))
	local processId = bit64.band(bit64.rshift(snowflake, 12), bit64.from_u32(0x1F))
	local increment = bit64.band(snowflake, bit64.from_u32(0x3F))

	return {
		timestamp = bit64.to_string(timestamp),
		workerId = bit64.to_string(workerId),
		processId = bit64.to_string(processId),
		increment = bit64.to_string(increment),
	}
end

--[=[
	@within Snowflake
	@param id string -- The snowflake ID
	@return Snowflake -- The decoded snowflake
]=]
function Snowflake.new(id: string)
	local self = setmetatable({}, {
		__index = Snowflake,

		__tostring = function(self)
			return self.id
		end,

		__eq = function(self, other)
			return self.id == other.id
		end,
	})

	self.id = id

	local decoded = decodeSnowflake(id)

	self.timestamp = decoded.timestamp
	self.workerId = decoded.workerId
	self.processId = decoded.processId
	self.increment = decoded.increment

	return self
end

return Snowflake


--[=[
    @class Sweeper
    A utility class for managing cleanup of objects and resources. Fork of Janitor by howmanysmall.
]=]

local Bot = require("../Lib/Bot")
local Task = require("../Util/Task")

local Sweeper = {}
Sweeper.__index = Sweeper

export type Sweeper = typeof(Sweeper) & {
	RemoveNoClean: (index: any) -> (),
	GetAll: () -> {[any]: any},
}

--[=[
    @return Sweeper
    Creates a new Sweeper instance.
]=]
function Sweeper.new() : Sweeper
	local self = setmetatable({}, Sweeper)

	self.CurrentlyCleaning = false
	self.Cache = {}

	local proxy = setmetatable({}, {
		__index = function(_, key)
			if key == "Cache" then return end

			return self[key]
		end,

		__newindex = function(_, key, value)
			if key == "CurrentlyCleaning" then return end

			if self[key] and type(self[key]) == "function" then
				error(`Cannot override method {key}.`)
			end

			self[key] = value
		end
	})

	return proxy
end

--[=[
    @within Sweeper
    @param object T -- The object to add
    @param methodName boolean | string? -- The cleanup method name or true for function/thread
    @param index any? -- Optional custom index
    @return T -- Returns the added object
    Adds an object to be cleaned up later.
]=]
function Sweeper:Add<T>(object: T, methodName: (boolean | string)?, index: any?) : T
	self.Cache[index or Bot.GenerateGUID()] = {
		method = methodName,
		object = object
	}

	return object
end

--[=[
    @within Sweeper
    Cleans up all objects in the Sweeper.
]=]
function Sweeper:Sweep() : ()
	for index in self.Cache do
		self:Remove(index)
	end

	self.Cache = {}
end

--[=[
    @within Sweeper
    @return {[any]: any} -- Table of all cached objects
    Returns a frozen clone of the cache.
]=]
function Sweeper:GetAll() : {[any]: any}
	return table.freeze(table.clone(self.Cache))
end

--[=[
    @within Sweeper
    @param index any -- The index to remove
    Removes an object from the cache without cleaning it.
]=]
function Sweeper:RemoveNoClean(index: any) : ()
	self.Cache[index] = nil
end

--[=[
    @within Sweeper
    @param index any -- The index to remove
    Removes and cleans up an object from the cache.
]=]
function Sweeper:Remove(index: any) : ()
	local data = self.Cache[index]
	if not data then return end

	local method = data.method
	if not method then
		method = "Disconnect"
	end

	if data.object[method] then
		data.object[method](data.object)

		return
	end

	if method == true then
		if typeof(data.object) == "function" then
			data.object()
		end

		if typeof(data.object) == "thread" then
			Task.Cancel(data.object)
		end
	end
end

return Sweeper


--[=[
    @class Timer
    A utility class for managing timed callbacks.
]=]
local Timer = {}
Timer.__index = Timer

export type Timer = typeof(Timer)

--[=[
    @param time number -- The total time to run in seconds
    @param interval number -- The interval between callbacks in seconds
    @param interval_callback ((totalTime: number) -> ())? -- Optional callback that runs each interval with total elapsed time
    @param end_callback (() -> ())? -- Optional callback that runs when timer completes
    @return Timer -- The new Timer instance
    Creates a new Timer that runs for the specified duration.
]=]
function Timer.new(time: number, interval: number, interval_callback: (totalTime: number) -> ()?, end_callback: () -> ()?) : Timer
    local self = setmetatable({}, Timer)

    self._interval = interval
    self._started = os.time()
    self._closed = false
    self._interval_callback = interval_callback
    self._end_callback = end_callback
    self._thread = coroutine.create(function()
        self:Run(time)
    end)

    coroutine.resume(self._thread)
    return self
end

--[=[
    @private
    The main run loop for the timer.
]=]
function Timer:Run(time: number)
    local startTime = os.time()
    while os.time() - startTime < time and not self._closed do
        local elapsed = os.time() - startTime
        self:Yield(self._interval)

        if self._interval_callback and not self._closed then
            self._interval_callback(elapsed)
        end
    end

    if self._end_callback and not self._closed then
        self._end_callback()
    end
end

--[=[
    @private
]=]
function Timer:Yield(duration: number)
    local start = os.clock()
    while os.clock() - start < duration do
        coroutine.yield()
    end
end

--[=[
    @within Timer
    Stops the timer and cancels any pending callbacks.
]=]
function Timer:Stop()
    self._closed = true
    if coroutine.status(self._thread) == "suspended" then
        coroutine.resume(self._thread)
    end
end

return Timer


local Vector3 = {}
Vector3.__index = Vector3

export type Vector3 = {
	X: number,
	Y: number,
	Z: number,
	Zero: () -> Vector3,
	One: () -> Vector3,
}

function Vector3.new(x: number, y: number, z: number) : Vector3
	local self = setmetatable({}, {
		__index = Vector3,
		__tostring = function(self)
			return string.format("(%d, %d, %d)", self.X, self.Y, self.Z)
		end,
		__eq = function(self, other)
			return self.X == other.X and self.Y == other.Y and self.Z == other.Z
		end,
		__add = function(self, other)
			return Vector3.new(self.X + other.X, self.Y + other.Y, self.Z + other.Z)
		end,
		__sub = function(self, other)
			return Vector3.new(self.X - other.X, self.Y - other.Y, self.Z - other.Z)
		end,
		__mul = function(self, other)
			return Vector3.new(self.X * other.X, self.Y * other.Y, self.Z * other.Z)
		end,
		__div = function(self, other)
			return Vector3.new(self.X / other.X, self.Y / other.Y, self.Z / other.Z)
		end,
		__unm = function(self)
			return Vector3.new(-self.X, -self.Y, -self.Z)
		end,
	})

	self.X = x
	self.Y = y
	self.Z = z

	return self
end

function Vector3.Zero() : Vector3
	return Vector3.new(0, 0, 0)
end

function Vector3.One() : Vector3
	return Vector3.new(1, 1, 1)
end

return Vector3


--[=[
	@class Bot
	The main Bot class that handles Discord gateway connections and core functionality.
]=]
local Bot = {}

local process = require("@lune/process")
local DiscordTypes = require("./DiscordTypes")

type WebSocket = {
	Close: () -> (),
	Next: () -> (any),
	Send: (data: string) -> ()
}

--[=[
	@prop DISCORD_API_VERSION string
	@within Bot
	The Discord API version being used.
]=]
Bot.DISCORD_API_VERSION = "10"

--[=[
	@prop ALOHA_VERSION string
	@within Bot
	The current version of Aloha.
]=]
Bot.ALOHA_VERSION = "0.4.5"

--[=[
	@prop PROCESS_ID number
	@within Bot
	The current process ID.
]=]
Bot.PROCESS_ID = 0

--[=[
	@prop TOKEN string
	@within Bot
	The bot's authentication token.
]=]
Bot.TOKEN = ""

--[=[
	@prop Client any
	@within Bot
	The Discord client instance.
]=]
Bot.Client = nil

--[=[
	@prop Shards {[number]: {Sent: number, Socket: WebSocket, ResumeURL: string, SessionID: string}}
	@within Bot
	Table of active shards and their data.
]=]
Bot.Shards = {} :: {[number]: {Sent: number, Socket: WebSocket, ResumeURL: string, SessionID: string}}

--[=[
	@prop SHARD_COUNT number
	@within Bot
	The total number of shards.
]=]
Bot.SHARD_COUNT = 0

--[=[
	@prop STARTED_AT number
	@within Bot
	Unix timestamp of when the bot started.
]=]
Bot.STARTED_AT = 0

--[=[
	@prop RATE_LIMIT number
	@within Bot
	Current rate limit value.
]=]
Bot.RATE_LIMIT = 0

--[=[
	@prop INTENTS number
	@within Bot
	The gateway intents being used.
]=]
Bot.INTENTS = 53608447

--[=[
	@prop GLOBAL_RATE_LIMIT boolean
	@within Bot
	Whether a global rate limit is active.
]=]
Bot.GLOBAL_RATE_LIMIT = false

--[=[
	@private
	@param seconds number -- Number of seconds to format
	@within Bot
	@return string -- Formatted time string
	Formats seconds into a human readable duration string.
]=]
local function formatTime(seconds: number): string
	local days = math.floor(seconds / 86400)
	seconds %= 86400
	local hours = math.floor(seconds / 3600)
	seconds %= 3600
	local minutes = math.floor(seconds / 60)
	seconds %= 60

	seconds = math.floor(seconds * 1000) / 1000

	local result = ""
	if days > 0 then
		result ..= days .. "d"
	end
	if hours > 0 then
		result ..= hours .. "h"
	end
	if minutes > 0 then
		result ..= minutes .. "m"
	end
	if seconds > 0 or #result == 0 then
		result ..= seconds .. "s"
	end

	return result
end

--[=[
	@prop OP {[string]: (...any) -> {op: number, d: any}}
	@within Bot
	Gateway operation code handlers.
]=]
Bot.OP = {
	-- Aloha!!
	["IDENTIFY"] = function(token: string, shard_id: number, shards: number, heartbeat_delay: number)
		print(`[Shard {shard_id}] 🔑 Identifying with intents {Bot.INTENTS}. Heartbeat in: {formatTime(heartbeat_delay)}`)
		return {
			op = 2,
			d = {
				token = token,
				properties = {
					os = process.os,
					browser = "aloha",
					device = "aloha"
				},
				intents = Bot.INTENTS,
				shard = {shard_id, shards},
				--compress = true
			}
		}
	end,

	["RESUME"] = function(token: string, sequence: number, shard_id: number)
		print(`[Shard {shard_id}] 🔑 Resuming session with sequence {sequence}...`)
		return {
			op = 6,
			d = {
				token = token,
				session_id = Bot.Shards[shard_id].SessionID,
				seq = sequence or false
			}
		}
	end,

	["HEARTBEAT"] = function(sequence: number)
		return {
			op = 1,
			d = sequence or false
		}
	end
}

--[=[
	@prop CLOSE_REASONS {[number]: string}
	@within Bot
	Mapping of WebSocket close codes to readable reasons.
]=]
Bot.CLOSE_REASONS = {
	[4000] = "UNKNOWN_ERROR",
	[4001] = "UNKNOWN_OPCODE",
	[4002] = "DECODE_ERROR",
	[4003] = "NOT_AUTHENTICATED",
	[4004] = "AUTHENTICATION_FAILED",
	[4005] = "ALREADY_AUTHENTICATED",
	[4007] = "INVALID_SEQUENCE",
	[4008] = "RATE_LIMITED",
	[4009] = "SESSION_TIMEOUT",
	[4010] = "INVALID_SHARD",
	[4011] = "SHARDING_REQUIRED",
	[4012] = "INVALID_API_VERSION",
	[4013] = "INVALID_INTENTS",
	[4014] = "DISALLOWED_INTENTS"
}

--[=[
	@within Bot
	@param wrapInBraces boolean? -- Whether to wrap the GUID in braces
	@return string -- The generated GUID
	Generates a random GUID string.
]=]
function Bot.GenerateGUID(wrapInBraces: boolean?) : string
	local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
	local function randomHex(char)
		local v = (char == 'x') and math.random(0, 15) or math.random(8, 11)
		return string.format("%x", v)
	end

	local s = string.gsub(template, "[xy]", randomHex)

	return wrapInBraces and `\{{s}\}` or s
end

--[=[
	@within Bot
	@param guild_id DiscordTypes.snowflake -- The guild ID to get shard for
	@return number? -- The shard ID for the guild
	Calculates which shard a guild belongs to.
]=]
function Bot.GetShard(guild_id: DiscordTypes.snowflake) : number?
	local id = tonumber(guild_id)
	if not id then return end

	local shard_id = bit32.rshift(id, 22) % Bot.SHARD_COUNT

	return shard_id
end

--[=[
	@within Bot
	@param enabled boolean -- Whether to enable global rate limiting
	Sets the global rate limit state.
]=]
function Bot.SetGlobalRateLimit(enabled: boolean)
	Bot.GLOBAL_RATE_LIMIT = enabled
end

return Bot


--[=[
@class Components
Module for handling Discord components.
]=]
local Components = {}
local DiscordTypes = require("./DiscordTypes")
local Objects = require("../Classes/Objects")
local Event = require("../Classes/Event")
local Enum = require("./Enum")
local Network = require("./Network")

type InteractionEvent = Event.Event<(Objects.Interaction, number?)>

local eventCallbacks = {}

for _, componentType in Enum.COMPONENT do
	eventCallbacks[componentType] = {
		event = Event.new()
	}
end

--[=[
@function GetInteractionSignal
@within Components
@param componentType number -- The type of component to get the signal for
@return Event<(Objects.Interaction, number?)> -- The event for the component type
]=]
function Components.GetInteractionSignal(componentType: number) : InteractionEvent
	return eventCallbacks[componentType].event
end

--[=[
@function HandleInteraction
@within Components
@param int DiscordTypes.InteractionData -- The interaction data
@param shard_id number? -- The shard ID
]=]
function Components.HandleInteraction(int: DiscordTypes.InteractionData, shard_id: number?)
	local interaction = Objects.Interaction.new(int)

	local componentData = interaction.data :: DiscordTypes.MessageComponentInteractionData

	eventCallbacks[componentData.component_type].event:Fire(interaction, shard_id)
end

return Components


--[=[
    @class Debug
    Handles debug functionality and user permissions for debugging
]=]
local Debug = {}
local Objects = require("../Classes/Objects")

--[=[
    @prop AllowedUsers {string}
    @within Debug
    List of user IDs that are allowed to use debug functionality
]=]
Debug.AllowedUsers = {}

--[=[
    @function AddUser
    @within Debug
    @param user Objects.User | string -- The user or user ID to add to allowed users
    Adds a user to the list of users allowed to use debug functionality
]=]
function Debug.AddUser(user: Objects.User | string)
	if type(user) == "string" then
		table.insert(Debug.AllowedUsers, user)
		return
	end

	if user.id then
		table.insert(Debug.AllowedUsers, user.id)
	end
end

return Debug


local LinkedList = require("../Classes/LinkedList")

local Cache = {
	Channels = {},
	Guilds = {},

	Users = LinkedList.new(),
	Members = LinkedList.new(),
	Messages = LinkedList.new()
}

return Cache


--[=[
    @class Mutex
    A mutex (mutual exclusion) implementation for coroutine synchronization. Based on SinisterRectus's implementation in Discordia.
]=]

local Task = require("../Util/Task")

local Mutex = {}
Mutex.__index = Mutex
Mutex.__len = function(self)
	return self.Length
end

local LinkedList = require("../Classes/LinkedList")
local Timer = require("../Classes/Timer")

type Timer = Timer.Timer

export type Mutex = {
	deck: {any},
	_active: boolean
}

setmetatable(Mutex, {__index = LinkedList})

--[=[
    @function new
    @within Mutex
    @return Mutex -- A new mutex instance
    Creates a new mutex instance
]=]
function Mutex.new()
	local self = setmetatable(LinkedList.new(), Mutex)
	self._active = false
	return self
end

--[=[
    @method Lock
    @within Mutex
    @param prepend boolean -- Whether to prepend the coroutine to the wait queue
    Locks the mutex. If already locked, yields the current coroutine until unlocked.
]=]
function Mutex:Lock(prepend: boolean)
	if not self._active then
		self._active = true
		return
	end

	if prepend then
		self:PushLeft(coroutine.running())
	else
		self:PushRight(coroutine.running())
	end

	while self._active do
		Task.Wait()
	end
end

--[=[
    @method Unlock
    @within Mutex
    @return boolean? -- Returns true if a coroutine was resumed, nil otherwise
    Unlocks the mutex and resumes the next waiting coroutine if any
]=]
function Mutex:Unlock() : boolean?
	if #self > 0 then
		return assert(coroutine.resume(self:PopLeft()))
	end

	self._active = false

	return nil
end

--[=[
    @method UnlockAfter
    @within Mutex
    @param delay number -- Delay in seconds before unlocking
    @return Timer -- Timer instance that will unlock the mutex after the delay
    Creates a timer that unlocks the mutex after the specified delay
]=]
function Mutex:UnlockAfter(delay: number) : Timer
	return Timer.new(delay * 1000, 1/1000, nil, self.Unlock)
end

return Mutex


local Task = {}
local Event = require("../Classes/Event")
local task = require("@lune/task")

function Task.Cancel(thread: thread)
	task.cancel(thread)
end

function Task.Delay<T...>(duration: number, functionOrThread: ((T...) -> ...any) | thread, ...: T...) : thread
	print("[Task] Delaying for ", duration, " seconds")
	return task.delay(duration, functionOrThread, ...)
end

function Task.Spawn<T...>(functionOrThread: ((T...) -> ...any) | thread, ...: T...) : thread
	return task.spawn(functionOrThread, ...)
end

function Task.Wait(duration: number?) : number
	return task.wait(duration)
end

function Task.Defer<T...>(functionOrThread: ((T...) -> ...any) | thread, ...: T...) : thread
	return task.defer(functionOrThread, ...)
end

return Task


--!optimize 2
--!strict

local Error = require("@packages/Error")

export type MissingError = {
	name: "MissingError",
	message: string,
	stack: string?,
}
type Static = typeof(Error) & {
	new: (message: string) -> MissingError,
}

local MissingError: MissingError & Static = setmetatable({}, {__index = Error}) :: any
(MissingError :: any).__index = MissingError;
(MissingError :: any).__tostring = function(self: MissingError): string
	local stack = self.stack
	if stack ~= nil and stack ~= "" then
		return stack
	end

	local message = self.message
	if message then
		return string.format("%*: %*", self.name, tostring(message))
	end

	return self.name
end

(MissingError :: any).new = function(message: string): MissingError
	local self = Error.new(message)
	self.name = "MissingError"
	return (setmetatable(self, MissingError) :: unknown) :: MissingError
end

return MissingError


--!optimize 2
--!strict

local Error = require("@packages/Error")

export type StrictTypeError = {
	name: "StrictTypeError",
	message: string,
	stack: string?,
}
type Static = typeof(Error) & {
	new: (message: string) -> StrictTypeError,
}

local StrictTypeError: StrictTypeError & Static = setmetatable({}, {__index = Error}) :: any
(StrictTypeError :: any).__index = StrictTypeError;
(StrictTypeError :: any).__tostring = function(self: StrictTypeError): string
	local stack = self.stack
	if stack ~= nil and stack ~= "" then
		return stack
	end

	local message = self.message
	if message then
		return string.format("%*: %*", self.name, tostring(message))
	end

	return self.name
end

(StrictTypeError :: any).new = function(message: string): StrictTypeError
	local self = Error.new(message)
	self.name = "StrictTypeError"
	return (setmetatable(self, StrictTypeError) :: unknown) :: StrictTypeError
end

return StrictTypeError


--!optimize 2
--!strict

local Error = require("@packages/Error")

export type TypeError = {
	name: "TypeError",
	message: string,
	stack: string?,
}
type Static = typeof(Error) & {
	new: (message: string) -> TypeError,
}

local TypeError: TypeError & Static = setmetatable({}, {__index = Error}) :: any;
(TypeError :: any).__index = TypeError;
(TypeError :: any).__tostring = function(self: TypeError): string
	local stack = self.stack
	if stack ~= nil and stack ~= "" then
		return stack
	end

	local message = self.message
	if message then
		return string.format("%*: %*", self.name, tostring(message))
	end

	return self.name
end;

(TypeError :: any).new = function(message: string): TypeError
	local self: TypeError = Error.new(message) :: never
	self.name = "TypeError"
	return setmetatable(self, TypeError) :: never
end

return TypeError


--!optimize 2
--!strict

local PathFileSystem = require("@packages/PathFileSystem")
local PathUtilities = require("@utilities/PathUtilities")
local process = require("@lune/process")

export type FileInstance = {
	FilePath: string,
	IsDirectory: boolean,
	Name: string,
	Parent: FileInstance?,

	GetChildren: (self: FileInstance) -> {FileInstance},
	GetDescendants: (self: FileInstance) -> {FileInstance},
	GetFullName: (self: FileInstance) -> string,

	FindFirstFile: (self: FileInstance, name: string, recursive: boolean?) -> FileInstance?,
	FindFirstDirectory: (self: FileInstance, name: string, recursive: boolean?) -> FileInstance?,

	Read: (self: FileInstance) -> string?,
	Write: (self: FileInstance, source: string) -> (),
	Destroy: (self: FileInstance) -> (),
}
type Private = {
	FilePath: string,
	IsDirectory: boolean,
	Name: string,
	Parent: FileInstance?,

	Children: {FileInstance}?,
	Descendants: {FileInstance}?,

	GetChildren: (self: Private) -> {FileInstance},
	GetDescendants: (self: Private) -> {FileInstance},
	GetFullName: (self: Private) -> string,

	FindFirstFile: (self: Private, name: string, recursive: boolean?) -> FileInstance?,
	FindFirstDirectory: (self: Private, name: string, recursive: boolean?) -> FileInstance?,

	Read: (self: Private) -> string?,
	Write: (self: Private, source: string) -> (),
	Destroy: (self: Private) -> (),
}
type Static = {
	ClassName: "FileInstance",
	Mark: (filePath: string, fileName: string?, parent: FileInstance?) -> FileInstance,
	Is: (value: any) -> boolean,
}
type PrivateStatic = Static & {
	__eq: (self: FileInstance, other: FileInstance) -> boolean,
	__tostring: (self: FileInstance) -> string,
}

local FileInstance = {} :: FileInstance & Static
local Private = FileInstance :: Private & PrivateStatic
FileInstance.ClassName = "FileInstance";
(FileInstance :: any).__index = FileInstance

function FileInstance.Mark(filePath, fileName, parent): FileInstance
	local newFilePath = if fileName then `{filePath}/{fileName}` else filePath

	local self: Private = setmetatable({}, FileInstance) :: never
	self.Children = nil
	self.Descendants = nil

	self.Name = PathUtilities.FileName(newFilePath)
	self.FilePath = newFilePath
	self.IsDirectory = PathFileSystem.IsDirectory(newFilePath)
	self.Parent = parent

	if not self.IsDirectory then
		-- if it's an init.luau file, the parent is actually the next parent up
		if self.Name == "init.luau" and parent then
			self.Parent = parent.Parent
		end
	end

	return self
end

function FileInstance.Is(value)
	return type(value) == "table" and getmetatable(value) == FileInstance
end

function FileInstance:GetFullName()
	local filePath = self.FilePath
	if string.sub(filePath, 1, 2) == "./" then
		filePath = string.sub(filePath, 3)
	end

	return process.cwd .. filePath
end

function Private:GetChildren()
	-- local cached = self.Children
	-- if cached then
	-- 	return cached
	-- end

	if self.IsDirectory then
		local inDirectory = PathFileSystem.ReadDirectory(self.FilePath)
		local children = table.create(#inDirectory)
		for index, child in inDirectory do
			children[index] = FileInstance.Mark(self.FilePath, child, self)
		end

		self.Children = children
		return children
	end

	local children = {}
	self.Children = children
	return children
end
function Private:GetDescendants()
	-- local cached = self.Descendants
	-- if cached then
	-- 	return cached
	-- end

	local descendants = self:GetChildren()
	local totalDescendants = #descendants
	local length = 0

	if totalDescendants > 0 then
		repeat
			length += 1
			local grandChildren = descendants[length]:GetChildren()
			for index, grandChild in grandChildren do
				descendants[totalDescendants + index] = grandChild
			end
			totalDescendants += #grandChildren
		until length == totalDescendants
	end

	self.Descendants = descendants
	return descendants
end

function FileInstance:FindFirstFile(name, recursive)
	if recursive then
		for _, descendant in self:GetDescendants() do
			if descendant.Name == name then
				return descendant
			end
		end

		return nil
	end

	for _, child in self:GetChildren() do
		if child.Name == name then
			return child
		end
	end

	return nil
end
function FileInstance:FindFirstDirectory(name, recursive)
	if recursive then
		for _, descendant in self:GetDescendants() do
			if descendant.IsDirectory and descendant.Name == name then
				return descendant
			end
		end

		return nil
	end

	for _, child in self:GetChildren() do
		if child.IsDirectory and child.Name == name then
			return child
		end
	end

	return nil
end

function FileInstance:Read()
	return if self.IsDirectory then nil else PathFileSystem.ReadFile(self.FilePath)
end
function FileInstance:Write(source)
	if not self.IsDirectory then
		PathFileSystem.WriteFile(self.FilePath, source)
	end
end

function FileInstance:Destroy()
	local filePath = self.FilePath
	if PathFileSystem.IsDirectory(filePath) then
		PathFileSystem.RemoveDirectory(filePath)
	elseif PathFileSystem.IsFile(filePath) then
		PathFileSystem.RemoveFile(filePath)
	end

	table.clear(self)
	setmetatable(self, nil)
end

function Private:__eq(other)
	return self.FilePath == other.FilePath
end
Private.__tostring = FileInstance.GetFullName
return table.freeze(FileInstance :: Static)


--!optimize 2
--!strict

local GreenTea = require("@packages/GreenTea")
local LuauPolyfill = require("@packages/LuauPolyfill")

local Error = LuauPolyfill.Error

type Node<K, V> = {
	Key: K,
	Next: Node<K, V>?,
	Previous: Node<K, V>?,
	Value: V,
}

export type LRUCache<K, V> = typeof(setmetatable(
	{} :: {
		Capacity: number,
		Size: number,

		Clear: (self: LRUCache<K, V>) -> (),
		Get: (self: LRUCache<K, V>, key: K) -> V?,
		Set: (self: LRUCache<K, V>, key: K, value: V) -> (),
	},
	{} :: {
		__len: (self: LRUCache<K, V>) -> number,
		__tostring: (self: LRUCache<K, V>) -> string,
	}
))
type Private<K, V> = typeof(setmetatable(
	{} :: {
		Capacity: number,
		Size: number,

		Clear: (self: Private<K, V>) -> (),
		Get: (self: Private<K, V>, key: K) -> V?,
		Set: (self: Private<K, V>, key: K, value: V) -> (),

		Head: Node<K, V>,
		NodesMap: {[K]: Node<K, V>},
		Tail: Node<K, V>,
	},
	{} :: {
		__len: (self: Private<K, V>) -> number,
		__tostring: (self: Private<K, V>) -> string,
	}
))

type Static = {
	ClassName: "LRUCache",
	new: <K, V>(capacity: number) -> LRUCache<K, V>,
	Is: (value: any) -> boolean,
	instanceof: (value: any) -> boolean,
}
type PrivateStatic = Static & {
	__len: (self: Private<any, any>) -> number,
	__tostring: (self: Private<any, any>) -> string,
}

--[=[
	The `LRUCache` (Least Recently Used Cache) class implements a fixed-size
	cache that stores key-value pairs. It uses the Least Recently Used eviction
	policy to manage the cache content when the capacity is exceeded. This
	means that when the cache reaches its capacity, it will remove the least
	recently accessed item to make room for new entries.

	### Features:

	- **Fast Retrieval:** Quickly access cached items using the `Get` method.
	- **Automatic Eviction:** Automatically evicts the least recently used item
	  when adding new entries beyond capacity.
	- **Mutable Size:** Check the current number of items with `Size`.
	- **Capacity Limit:** Set a maximum capacity to control memory usage.
	- **Supports Debug Mode:** You can set `_G.__DEV__` to `true` to enable
	  debug mode. This has to be done BEFORE requiring the module.
	- **Supports `__len`:** Use the length operator `#` to get the cache size.
	- **Supports `__tostring`:** You can call `tostring` on the cache.

	### Example Usage:

	```lua
	_G.__DEV__ = RunService:IsStudio()
	local DataStructures = require("DataStructures")

	-- Create a new cache with a capacity of 2
	local lruCache = DataStructures.LRUCache.new(2)

	-- Add some key-value pairs
	lruCache:Set("apple", "red")
	lruCache:Set("banana", "yellow")

	print(lruCache:Get("apple")) -- Output: "red"
	print(lruCache:Get("banana")) -- Output: "yellow"

	-- Add another item, exceeding the capacity
	lruCache:Set("cherry", "red")

	-- Since "apple" was least recently used, it gets evicted
	print(lruCache:Get("apple")) -- Output: nil
	print(lruCache:Get("cherry")) -- Output: "red"

	-- Check the size of the cache
	print(#lruCache, lruCache.Size) -- Output: 2, 2

	-- Clear the cache
	lruCache:Clear()
	print(#lruCache, lruCache.Size) -- Output: 0, 0
	```

	### Methods:

	- **`LRUCache.new(capacity: number): LRUCache`**
		- Creates a new `LRUCache` instance with the specified capacity.
		- **Parameters:**
			- `capacity` (number): The maximum number of items the cache can
			  hold.
		- **Returns:** A new `LRUCache` object.

	- **`LRUCache:Get(key: K): V?`**
		- Retrieves the value associated with the given key.
		- Marks the key as recently used.
		- **Parameters:**
			- `key` (K): The key to look up.
		- **Returns:** The value associated with the key, or `nil` if not found.

	- **`LRUCache:Set(key: K, value: V): ()`**
		- Adds or updates a key-value pair in the cache.
		- If the key already exists, its value is updated, and it is marked as
		  recently used.
		- If adding the new key exceeds the capacity, the least recently used
		  item is evicted.
		- **Parameters:**
			- `key` (K): The key to insert or update.
			- `value` (V): The value to associate with the key.

	- **`LRUCache:Clear(): ()`**
		- Removes all items from the cache.

	### Properties:
	- **`LRUCache.Capacity: number`** (read-only)
		- The maximum number of items the cache can hold.

	- **`LRUCache.Size: number`**
		- The current number of items in the cache.

	### Tags:

	- `Supports Debug Mode`: You can set `_G.__DEV__` to `true` to enable
	  debug mode. This has to be done BEFORE requiring the module.
	- `Supports __len`: You can use the `#` operator to get the current size
	  of the cache.
	- `Supports __tostring`: You can call `tostring` on the cache.

	@tag Supports Debug Mode
	@tag Supports __len
	@tag Supports __tostring
	@class LRUCache
]=]
local LRUCache = {} :: LRUCache<any, any> & Static
local Private = LRUCache :: Private<any, any> & PrivateStatic
LRUCache.ClassName = "LRUCache";
(LRUCache :: any).__index = LRUCache

--[=[
	The maximum number of items the cache can hold.
	@prop Capacity integer
	@readonly
	@within LRUCache
]=]
--[=[
	The current number of items in the cache.
	@prop Size integer
	@readonly
	@within LRUCache
]=]

local cached: boolean = nil
local function IsUltraStrictMode(): boolean
	if cached ~= nil then
		return cached
	end

	local isUltraStrictMode = not not _G.__DEV__
	cached = isUltraStrictMode
	return isUltraStrictMode
end
local IsValidCapacity = GreenTea.build(GreenTea.number({integer = true, range = "(0, ]"}))
local IsUnknown = GreenTea.build(GreenTea.unknown())

--[=[
	Creates a new LRU cache.

	@error DebugTypeError -- Thrown when you are using debug mode and you pass an incorrect type.

	@param capacity number -- The maximum number of items the cache can hold.
	@return LRUCache<K, V>
]=]
function LRUCache.new<K, V>(capacity: number): LRUCache<K, V>
	if IsUltraStrictMode() then
		local typeSuccess, typeError = IsValidCapacity:matches(capacity)
		if not typeSuccess then
			local exception = Error.new(tostring(typeError))
			exception.name = "DebugTypeError"
			Error.captureStackTrace(exception, LRUCache.new)
			error(exception)
		end
	end

	local self: Private<K, V> = setmetatable({}, LRUCache) :: never
	self.Capacity = capacity
	self.Size = 0

	self.Head = {} :: Node<K, V>
	self.NodesMap = {}
	self.Tail = {} :: Node<K, V>

	return self
end

--[=[
	Checks if `value` is literally an LRUCache.

	```lua
	LRUCache.Is(LRUCache.new(10)) -- true
	LRUCache.Is(LRUCache) -- false
	LRUCache.Is({}) -- false
	LRUCache.Is(nil) -- false
	LRUCache.Is(1) -- false
	```

	@param value unknown
	@return boolean
]=]
function LRUCache.Is(value: any)
	return type(value) == "table" and getmetatable(value) == LRUCache
end
--[=[
	An alias for [LRUCache.Is].

	@function instanceof
	@within LRUCache

	@param value unknown
	@return boolean
]=]
LRUCache.instanceof = LRUCache.Is

local function Evict<K, V>(self: Private<K, V>, node: Node<K, V>)
	local nodesMap = self.NodesMap
	local key = node.Key
	if nodesMap[key] == nil then
		return
	end

	nodesMap[key] = nil
	self.Size -= 1

	local previousNode = node.Previous :: Node<K, V>
	local nextNode = node.Next :: Node<K, V>

	local head = self.Head
	local tail = self.Tail

	if previousNode == head and nextNode == tail then
		head.Next = nil
		tail.Previous = nil
		self.Size = 0
		return
	end

	if previousNode == head then
		nextNode.Previous = head
		head.Next = nextNode
		return
	end

	if nextNode == tail then
		previousNode.Next = tail
		tail.Previous = previousNode
		return
	end

	previousNode.Next = nextNode
	nextNode.Previous = previousNode
end
local function Append<K, V>(self: Private<K, V>, node: Node<K, V>)
	self.NodesMap[node.Key] = node

	local head = self.Head
	local tail = self.Tail

	if head.Next then
		local tailPrevious = tail.Previous :: Node<K, V>
		tailPrevious.Next = node
		node.Previous = tailPrevious
		node.Next = tail
		tail.Previous = node
	else
		head.Next = node
		tail.Previous = node
		node.Previous = head
		node.Next = tail
	end

	local size = self.Size + 1
	self.Size = size
	if size > self.Capacity then
		Evict(self, head.Next :: Node<K, V>)
	end
end

--[=[
	Gets the value associated with the given key.

	@error DebugTypeError -- Thrown when you are using debug mode and you pass an incorrect type.

	@method Get
	@within LRUCache

	@param key K -- The key to get the value for.
	@return V?
]=]
function Private:Get(key)
	if IsUltraStrictMode() then
		local typeSuccess, typeError = IsUnknown:matches(key)
		if not typeSuccess then
			local exception = Error.new(tostring(typeError))
			exception.name = "DebugTypeError"
			Error.captureStackTrace(exception, LRUCache.Get)
			error(exception)
		end
	end

	local node = self.NodesMap[key]
	if not node then
		return nil
	end

	Evict(self, node)
	Append(self, node)
	return node.Value
end

--[=[
	Sets the value to the given key.

	@error DebugTypeError -- Thrown when you are using debug mode and you pass an incorrect type.

	@method Set
	@within LRUCache

	@param key K -- The key to set the value to.
	@param value V -- The value to set.
]=]
function Private:Set<K, V>(key, value)
	if IsUltraStrictMode() then
		local typeSuccess0, typeError0 = IsUnknown:matches(key)
		if not typeSuccess0 then
			local exception = Error.new(tostring(typeError0))
			exception.name = "DebugTypeError"
			Error.captureStackTrace(exception, LRUCache.Set)
			error(exception)
		end
		local typeSuccess1, typeError1 = IsUnknown:matches(value)
		if not typeSuccess1 then
			local exception = Error.new(tostring(typeError1))
			exception.name = "DebugTypeError"
			Error.captureStackTrace(exception, LRUCache.Set)
			error(exception)
		end
	end

	local node = self.NodesMap[key]
	if node then
		node.Value = value
		Evict(self, node)
		Append(self, node)
	else
		Append(self, {Key = key, Value = value})
	end
end

--[=[
	Clears the cache.

	@method Clear
	@within LRUCache
]=]
function Private:Clear()
	table.clear(self.NodesMap)
	table.clear(self.Head)
	table.clear(self.Tail)
	self.Size = 0
end

function Private:__len()
	return self.Size
end
function Private:__tostring()
	return "LRUCache"
end

return table.freeze(LRUCache :: Static)


--!optimize 2
--!strict

local Color3 = require("@contexts/Color3")
local SortedArray = require("@packages/SortedArray")

export type Color3 = Color3.PolyfillColor3
export type ColorData = {
	Color3: Color3,
	Name: string,
	Number: number,
}

local function DeepCopy<T>(object: T): T
	local new = table.clone(object :: never)
	for index, value in object :: never do
		if type(value) == "table" then
			new[index] = DeepCopy(value)
		end
	end
	return new :: never
end

local BASE_DATA: {ColorData} = {
	{
		Color3 = Color3.fromRGB(242, 243, 243);
		Name = "White";
		Number = 1;
	};
	{
		Color3 = Color3.fromRGB(161, 165, 162);
		Name = "Grey";
		Number = 2;
	};
	{
		Color3 = Color3.fromRGB(249, 233, 153);
		Name = "Light yellow";
		Number = 3;
	};
	{
		Color3 = Color3.fromRGB(215, 197, 154);
		Name = "Brick yellow";
		Number = 5;
	};
	{
		Color3 = Color3.fromRGB(194, 218, 184);
		Name = "Light green (Mint)";
		Number = 6;
	};
	{
		Color3 = Color3.fromRGB(232, 186, 200);
		Name = "Light reddish violet";
		Number = 9;
	};
	{
		Color3 = Color3.fromRGB(128, 187, 219);
		Name = "Pastel Blue";
		Number = 11;
	};
	{
		Color3 = Color3.fromRGB(203, 132, 66);
		Name = "Light orange brown";
		Number = 12;
	};
	{
		Color3 = Color3.fromRGB(204, 142, 105);
		Name = "Nougat";
		Number = 18;
	};
	{
		Color3 = Color3.fromRGB(196, 40, 28);
		Name = "Bright red";
		Number = 21;
	};
	{
		Color3 = Color3.fromRGB(196, 112, 160);
		Name = "Med. reddish violet";
		Number = 22;
	};
	{
		Color3 = Color3.fromRGB(13, 105, 172);
		Name = "Bright blue";
		Number = 23;
	};
	{
		Color3 = Color3.fromRGB(245, 205, 48);
		Name = "Bright yellow";
		Number = 24;
	};
	{
		Color3 = Color3.fromRGB(98, 71, 50);
		Name = "Earth orange";
		Number = 25;
	};
	{
		Color3 = Color3.fromRGB(27, 42, 53);
		Name = "Black";
		Number = 26;
	};
	{
		Color3 = Color3.fromRGB(109, 110, 108);
		Name = "Dark grey";
		Number = 27;
	};
	{
		Color3 = Color3.fromRGB(40, 127, 71);
		Name = "Dark green";
		Number = 28;
	};
	{
		Color3 = Color3.fromRGB(161, 196, 140);
		Name = "Medium green";
		Number = 29;
	};
	{
		Color3 = Color3.fromRGB(243, 207, 155);
		Name = "Lig. Yellowich orange";
		Number = 36;
	};
	{
		Color3 = Color3.fromRGB(75, 151, 75);
		Name = "Bright green";
		Number = 37;
	};
	{
		Color3 = Color3.fromRGB(160, 95, 53);
		Name = "Dark orange";
		Number = 38;
	};
	{
		Color3 = Color3.fromRGB(193, 202, 222);
		Name = "Light bluish violet";
		Number = 39;
	};
	{
		Color3 = Color3.fromRGB(236, 236, 236);
		Name = "Transparent";
		Number = 40;
	};
	{
		Color3 = Color3.fromRGB(205, 84, 75);
		Name = "Tr. Red";
		Number = 41;
	};
	{
		Color3 = Color3.fromRGB(193, 223, 240);
		Name = "Tr. Lg blue";
		Number = 42;
	};
	{
		Color3 = Color3.fromRGB(123, 182, 232);
		Name = "Tr. Blue";
		Number = 43;
	};
	{
		Color3 = Color3.fromRGB(247, 241, 141);
		Name = "Tr. Yellow";
		Number = 44;
	};
	{
		Color3 = Color3.fromRGB(180, 210, 228);
		Name = "Light blue";
		Number = 45;
	};
	{
		Color3 = Color3.fromRGB(217, 133, 108);
		Name = "Tr. Flu. Reddish orange";
		Number = 47;
	};
	{
		Color3 = Color3.fromRGB(132, 182, 141);
		Name = "Tr. Green";
		Number = 48;
	};
	{
		Color3 = Color3.fromRGB(248, 241, 132);
		Name = "Tr. Flu. Green";
		Number = 49;
	};
	{
		Color3 = Color3.fromRGB(236, 232, 222);
		Name = "Phosph. White";
		Number = 50;
	};
	{
		Color3 = Color3.fromRGB(238, 196, 182);
		Name = "Light red";
		Number = 100;
	};
	{
		Color3 = Color3.fromRGB(218, 134, 122);
		Name = "Medium red";
		Number = 101;
	};
	{
		Color3 = Color3.fromRGB(110, 153, 202);
		Name = "Medium blue";
		Number = 102;
	};
	{
		Color3 = Color3.fromRGB(199, 193, 183);
		Name = "Light grey";
		Number = 103;
	};
	{
		Color3 = Color3.fromRGB(107, 50, 124);
		Name = "Bright violet";
		Number = 104;
	};
	{
		Color3 = Color3.fromRGB(226, 155, 64);
		Name = "Br. yellowish orange";
		Number = 105;
	};
	{
		Color3 = Color3.fromRGB(218, 133, 65);
		Name = "Bright orange";
		Number = 106;
	};
	{
		Color3 = Color3.fromRGB(0, 143, 156);
		Name = "Bright bluish green";
		Number = 107;
	};
	{
		Color3 = Color3.fromRGB(104, 92, 67);
		Name = "Earth yellow";
		Number = 108;
	};
	{
		Color3 = Color3.fromRGB(67, 84, 147);
		Name = "Bright bluish violet";
		Number = 110;
	};
	{
		Color3 = Color3.fromRGB(191, 183, 177);
		Name = "Tr. Brown";
		Number = 111;
	};
	{
		Color3 = Color3.fromRGB(104, 116, 172);
		Name = "Medium bluish violet";
		Number = 112;
	};
	{
		Color3 = Color3.fromRGB(229, 173, 200);
		Name = "Tr. Medi. reddish violet";
		Number = 113;
	};
	{
		Color3 = Color3.fromRGB(199, 210, 60);
		Name = "Med. yellowish green";
		Number = 115;
	};
	{
		Color3 = Color3.fromRGB(85, 165, 175);
		Name = "Med. bluish green";
		Number = 116;
	};
	{
		Color3 = Color3.fromRGB(183, 215, 213);
		Name = "Light bluish green";
		Number = 118;
	};
	{
		Color3 = Color3.fromRGB(164, 189, 71);
		Name = "Br. yellowish green";
		Number = 119;
	};
	{
		Color3 = Color3.fromRGB(217, 228, 167);
		Name = "Lig. yellowish green";
		Number = 120;
	};
	{
		Color3 = Color3.fromRGB(231, 172, 88);
		Name = "Med. yellowish orange";
		Number = 121;
	};
	{
		Color3 = Color3.fromRGB(211, 111, 76);
		Name = "Br. reddish orange";
		Number = 123;
	};
	{
		Color3 = Color3.fromRGB(146, 57, 120);
		Name = "Bright reddish violet";
		Number = 124;
	};
	{
		Color3 = Color3.fromRGB(234, 184, 146);
		Name = "Light orange";
		Number = 125;
	};
	{
		Color3 = Color3.fromRGB(165, 165, 203);
		Name = "Tr. Bright bluish violet";
		Number = 126;
	};
	{
		Color3 = Color3.fromRGB(220, 188, 129);
		Name = "Gold";
		Number = 127;
	};
	{
		Color3 = Color3.fromRGB(174, 122, 89);
		Name = "Dark nougat";
		Number = 128;
	};
	{
		Color3 = Color3.fromRGB(156, 163, 168);
		Name = "Silver";
		Number = 131;
	};
	{
		Color3 = Color3.fromRGB(213, 115, 61);
		Name = "Neon orange";
		Number = 133;
	};
	{
		Color3 = Color3.fromRGB(216, 221, 86);
		Name = "Neon green";
		Number = 134;
	};
	{
		Color3 = Color3.fromRGB(116, 134, 157);
		Name = "Sand blue";
		Number = 135;
	};
	{
		Color3 = Color3.fromRGB(135, 124, 144);
		Name = "Sand violet";
		Number = 136;
	};
	{
		Color3 = Color3.fromRGB(224, 152, 100);
		Name = "Medium orange";
		Number = 137;
	};
	{
		Color3 = Color3.fromRGB(149, 138, 115);
		Name = "Sand yellow";
		Number = 138;
	};
	{
		Color3 = Color3.fromRGB(32, 58, 86);
		Name = "Earth blue";
		Number = 140;
	};
	{
		Color3 = Color3.fromRGB(39, 70, 45);
		Name = "Earth green";
		Number = 141;
	};
	{
		Color3 = Color3.fromRGB(207, 226, 247);
		Name = "Tr. Flu. Blue";
		Number = 143;
	};
	{
		Color3 = Color3.fromRGB(121, 136, 161);
		Name = "Sand blue metallic";
		Number = 145;
	};
	{
		Color3 = Color3.fromRGB(149, 142, 163);
		Name = "Sand violet metallic";
		Number = 146;
	};
	{
		Color3 = Color3.fromRGB(147, 135, 103);
		Name = "Sand yellow metallic";
		Number = 147;
	};
	{
		Color3 = Color3.fromRGB(87, 88, 87);
		Name = "Dark grey metallic";
		Number = 148;
	};
	{
		Color3 = Color3.fromRGB(22, 29, 50);
		Name = "Black metallic";
		Number = 149;
	};
	{
		Color3 = Color3.fromRGB(171, 173, 172);
		Name = "Light grey metallic";
		Number = 150;
	};
	{
		Color3 = Color3.fromRGB(120, 144, 130);
		Name = "Sand green";
		Number = 151;
	};
	{
		Color3 = Color3.fromRGB(149, 121, 119);
		Name = "Sand red";
		Number = 153;
	};
	{
		Color3 = Color3.fromRGB(123, 46, 47);
		Name = "Dark red";
		Number = 154;
	};
	{
		Color3 = Color3.fromRGB(255, 246, 123);
		Name = "Tr. Flu. Yellow";
		Number = 157;
	};
	{
		Color3 = Color3.fromRGB(225, 164, 194);
		Name = "Tr. Flu. Red";
		Number = 158;
	};
	{
		Color3 = Color3.fromRGB(117, 108, 98);
		Name = "Gun metallic";
		Number = 168;
	};
	{
		Color3 = Color3.fromRGB(151, 105, 91);
		Name = "Red flip/flop";
		Number = 176;
	};
	{
		Color3 = Color3.fromRGB(180, 132, 85);
		Name = "Yellow flip/flop";
		Number = 178;
	};
	{
		Color3 = Color3.fromRGB(137, 135, 136);
		Name = "Silver flip/flop";
		Number = 179;
	};
	{
		Color3 = Color3.fromRGB(215, 169, 75);
		Name = "Curry";
		Number = 180;
	};
	{
		Color3 = Color3.fromRGB(249, 214, 46);
		Name = "Fire Yellow";
		Number = 190;
	};
	{
		Color3 = Color3.fromRGB(232, 171, 45);
		Name = "Flame yellowish orange";
		Number = 191;
	};
	{
		Color3 = Color3.fromRGB(105, 64, 40);
		Name = "Reddish brown";
		Number = 192;
	};
	{
		Color3 = Color3.fromRGB(207, 96, 36);
		Name = "Flame reddish orange";
		Number = 193;
	};
	{
		Color3 = Color3.fromRGB(163, 162, 165);
		Name = "Medium stone grey";
		Number = 194;
	};
	{
		Color3 = Color3.fromRGB(70, 103, 164);
		Name = "Royal blue";
		Number = 195;
	};
	{
		Color3 = Color3.fromRGB(35, 71, 139);
		Name = "Dark Royal blue";
		Number = 196;
	};
	{
		Color3 = Color3.fromRGB(142, 66, 133);
		Name = "Bright reddish lilac";
		Number = 198;
	};
	{
		Color3 = Color3.fromRGB(99, 95, 98);
		Name = "Dark stone grey";
		Number = 199;
	};
	{
		Color3 = Color3.fromRGB(130, 138, 93);
		Name = "Lemon metalic";
		Number = 200;
	};
	{
		Color3 = Color3.fromRGB(229, 228, 223);
		Name = "Light stone grey";
		Number = 208;
	};
	{
		Color3 = Color3.fromRGB(176, 142, 68);
		Name = "Dark Curry";
		Number = 209;
	};
	{
		Color3 = Color3.fromRGB(112, 149, 120);
		Name = "Faded green";
		Number = 210;
	};
	{
		Color3 = Color3.fromRGB(121, 181, 181);
		Name = "Turquoise";
		Number = 211;
	};
	{
		Color3 = Color3.fromRGB(159, 195, 233);
		Name = "Light Royal blue";
		Number = 212;
	};
	{
		Color3 = Color3.fromRGB(108, 129, 183);
		Name = "Medium Royal blue";
		Number = 213;
	};
	{
		Color3 = Color3.fromRGB(144, 76, 42);
		Name = "Rust";
		Number = 216;
	};
	{
		Color3 = Color3.fromRGB(124, 92, 70);
		Name = "Brown";
		Number = 217;
	};
	{
		Color3 = Color3.fromRGB(150, 112, 159);
		Name = "Reddish lilac";
		Number = 218;
	};
	{
		Color3 = Color3.fromRGB(107, 98, 155);
		Name = "Lilac";
		Number = 219;
	};
	{
		Color3 = Color3.fromRGB(167, 169, 206);
		Name = "Light lilac";
		Number = 220;
	};
	{
		Color3 = Color3.fromRGB(205, 98, 152);
		Name = "Bright purple";
		Number = 221;
	};
	{
		Color3 = Color3.fromRGB(228, 173, 200);
		Name = "Light purple";
		Number = 222;
	};
	{
		Color3 = Color3.fromRGB(220, 144, 149);
		Name = "Light pink";
		Number = 223;
	};
	{
		Color3 = Color3.fromRGB(240, 213, 160);
		Name = "Light brick yellow";
		Number = 224;
	};
	{
		Color3 = Color3.fromRGB(235, 184, 127);
		Name = "Warm yellowish orange";
		Number = 225;
	};
	{
		Color3 = Color3.fromRGB(253, 234, 141);
		Name = "Cool yellow";
		Number = 226;
	};
	{
		Color3 = Color3.fromRGB(125, 187, 221);
		Name = "Dove blue";
		Number = 232;
	};
	{
		Color3 = Color3.fromRGB(52, 43, 117);
		Name = "Medium lilac";
		Number = 268;
	};
	{
		Color3 = Color3.fromRGB(80, 109, 84);
		Name = "Slime green";
		Number = 301;
	};
	{
		Color3 = Color3.fromRGB(91, 93, 105);
		Name = "Smoky grey";
		Number = 302;
	};
	{
		Color3 = Color3.fromRGB(0, 16, 176);
		Name = "Dark blue";
		Number = 303;
	};
	{
		Color3 = Color3.fromRGB(44, 101, 29);
		Name = "Parsley green";
		Number = 304;
	};
	{
		Color3 = Color3.fromRGB(82, 124, 174);
		Name = "Steel blue";
		Number = 305;
	};
	{
		Color3 = Color3.fromRGB(51, 88, 130);
		Name = "Storm blue";
		Number = 306;
	};
	{
		Color3 = Color3.fromRGB(16, 42, 220);
		Name = "Lapis";
		Number = 307;
	};
	{
		Color3 = Color3.fromRGB(61, 21, 133);
		Name = "Dark indigo";
		Number = 308;
	};
	{
		Color3 = Color3.fromRGB(52, 142, 64);
		Name = "Sea green";
		Number = 309;
	};
	{
		Color3 = Color3.fromRGB(91, 154, 76);
		Name = "Shamrock";
		Number = 310;
	};
	{
		Color3 = Color3.fromRGB(159, 161, 172);
		Name = "Fossil";
		Number = 311;
	};
	{
		Color3 = Color3.fromRGB(89, 34, 89);
		Name = "Mulberry";
		Number = 312;
	};
	{
		Color3 = Color3.fromRGB(31, 128, 29);
		Name = "Forest green";
		Number = 313;
	};
	{
		Color3 = Color3.fromRGB(159, 173, 192);
		Name = "Cadet blue";
		Number = 314;
	};
	{
		Color3 = Color3.fromRGB(9, 137, 207);
		Name = "Electric blue";
		Number = 315;
	};
	{
		Color3 = Color3.fromRGB(123, 0, 123);
		Name = "Eggplant";
		Number = 316;
	};
	{
		Color3 = Color3.fromRGB(124, 156, 107);
		Name = "Moss";
		Number = 317;
	};
	{
		Color3 = Color3.fromRGB(138, 171, 133);
		Name = "Artichoke";
		Number = 318;
	};
	{
		Color3 = Color3.fromRGB(185, 196, 177);
		Name = "Sage green";
		Number = 319;
	};
	{
		Color3 = Color3.fromRGB(202, 203, 209);
		Name = "Ghost grey";
		Number = 320;
	};
	{
		Color3 = Color3.fromRGB(167, 94, 155);
		Name = "Lilac";
		Number = 321;
	};
	{
		Color3 = Color3.fromRGB(123, 47, 123);
		Name = "Plum";
		Number = 322;
	};
	{
		Color3 = Color3.fromRGB(148, 190, 129);
		Name = "Olivine";
		Number = 323;
	};
	{
		Color3 = Color3.fromRGB(168, 189, 153);
		Name = "Laurel green";
		Number = 324;
	};
	{
		Color3 = Color3.fromRGB(223, 223, 222);
		Name = "Quill grey";
		Number = 325;
	};
	{
		Color3 = Color3.fromRGB(151, 0, 0);
		Name = "Crimson";
		Number = 327;
	};
	{
		Color3 = Color3.fromRGB(177, 229, 166);
		Name = "Mint";
		Number = 328;
	};
	{
		Color3 = Color3.fromRGB(152, 194, 219);
		Name = "Baby blue";
		Number = 329;
	};
	{
		Color3 = Color3.fromRGB(255, 152, 220);
		Name = "Carnation pink";
		Number = 330;
	};
	{
		Color3 = Color3.fromRGB(255, 89, 89);
		Name = "Persimmon";
		Number = 331;
	};
	{
		Color3 = Color3.fromRGB(117, 0, 0);
		Name = "Maroon";
		Number = 332;
	};
	{
		Color3 = Color3.fromRGB(239, 184, 56);
		Name = "Gold";
		Number = 333;
	};
	{
		Color3 = Color3.fromRGB(248, 217, 109);
		Name = "Daisy orange";
		Number = 334;
	};
	{
		Color3 = Color3.fromRGB(231, 231, 236);
		Name = "Pearl";
		Number = 335;
	};
	{
		Color3 = Color3.fromRGB(199, 212, 228);
		Name = "Fog";
		Number = 336;
	};
	{
		Color3 = Color3.fromRGB(255, 148, 148);
		Name = "Salmon";
		Number = 337;
	};
	{
		Color3 = Color3.fromRGB(190, 104, 98);
		Name = "Terra Cotta";
		Number = 338;
	};
	{
		Color3 = Color3.fromRGB(86, 36, 36);
		Name = "Cocoa";
		Number = 339;
	};
	{
		Color3 = Color3.fromRGB(241, 231, 199);
		Name = "Wheat";
		Number = 340;
	};
	{
		Color3 = Color3.fromRGB(254, 243, 187);
		Name = "Buttermilk";
		Number = 341;
	};
	{
		Color3 = Color3.fromRGB(224, 178, 208);
		Name = "Mauve";
		Number = 342;
	};
	{
		Color3 = Color3.fromRGB(212, 144, 189);
		Name = "Sunrise";
		Number = 343;
	};
	{
		Color3 = Color3.fromRGB(150, 85, 85);
		Name = "Tawny";
		Number = 344;
	};
	{
		Color3 = Color3.fromRGB(143, 76, 42);
		Name = "Rust";
		Number = 345;
	};
	{
		Color3 = Color3.fromRGB(211, 190, 150);
		Name = "Cashmere";
		Number = 346;
	};
	{
		Color3 = Color3.fromRGB(226, 220, 188);
		Name = "Khaki";
		Number = 347;
	};
	{
		Color3 = Color3.fromRGB(237, 234, 234);
		Name = "Lily white";
		Number = 348;
	};
	{
		Color3 = Color3.fromRGB(233, 218, 218);
		Name = "Seashell";
		Number = 349;
	};
	{
		Color3 = Color3.fromRGB(136, 62, 62);
		Name = "Burgundy";
		Number = 350;
	};
	{
		Color3 = Color3.fromRGB(188, 155, 93);
		Name = "Cork";
		Number = 351;
	};
	{
		Color3 = Color3.fromRGB(199, 172, 120);
		Name = "Burlap";
		Number = 352;
	};
	{
		Color3 = Color3.fromRGB(202, 191, 163);
		Name = "Beige";
		Number = 353;
	};
	{
		Color3 = Color3.fromRGB(187, 179, 178);
		Name = "Oyster";
		Number = 354;
	};
	{
		Color3 = Color3.fromRGB(108, 88, 75);
		Name = "Pine Cone";
		Number = 355;
	};
	{
		Color3 = Color3.fromRGB(160, 132, 79);
		Name = "Fawn brown";
		Number = 356;
	};
	{
		Color3 = Color3.fromRGB(149, 137, 136);
		Name = "Hurricane grey";
		Number = 357;
	};
	{
		Color3 = Color3.fromRGB(171, 168, 158);
		Name = "Cloudy grey";
		Number = 358;
	};
	{
		Color3 = Color3.fromRGB(175, 148, 131);
		Name = "Linen";
		Number = 359;
	};
	{
		Color3 = Color3.fromRGB(150, 103, 102);
		Name = "Copper";
		Number = 360;
	};
	{
		Color3 = Color3.fromRGB(86, 66, 54);
		Name = "Medium brown";
		Number = 361;
	};
	{
		Color3 = Color3.fromRGB(126, 104, 63);
		Name = "Bronze";
		Number = 362;
	};
	{
		Color3 = Color3.fromRGB(105, 102, 92);
		Name = "Flint";
		Number = 363;
	};
	{
		Color3 = Color3.fromRGB(90, 76, 66);
		Name = "Dark taupe";
		Number = 364;
	};
	{
		Color3 = Color3.fromRGB(106, 57, 9);
		Name = "Burnt Sienna";
		Number = 365;
	};
	{
		Color3 = Color3.fromRGB(248, 248, 248);
		Name = "Institutional white";
		Number = 1001;
	};
	{
		Color3 = Color3.fromRGB(205, 205, 205);
		Name = "Mid gray";
		Number = 1002;
	};
	{
		Color3 = Color3.fromRGB(17, 17, 17);
		Name = "Really black";
		Number = 1003;
	};
	{
		Color3 = Color3.fromRGB(255, 0, 0);
		Name = "Really red";
		Number = 1004;
	};
	{
		Color3 = Color3.fromRGB(255, 176, 0);
		Name = "Deep orange";
		Number = 1005;
	};
	{
		Color3 = Color3.fromRGB(180, 128, 255);
		Name = "Alder";
		Number = 1006;
	};
	{
		Color3 = Color3.fromRGB(163, 75, 75);
		Name = "Dusty Rose";
		Number = 1007;
	};
	{
		Color3 = Color3.fromRGB(193, 190, 66);
		Name = "Olive";
		Number = 1008;
	};
	{
		Color3 = Color3.fromRGB(255, 255, 0);
		Name = "New Yeller";
		Number = 1009;
	};
	{
		Color3 = Color3.fromRGB(0, 0, 255);
		Name = "Really blue";
		Number = 1010;
	};
	{
		Color3 = Color3.fromRGB(0, 32, 96);
		Name = "Navy blue";
		Number = 1011;
	};
	{
		Color3 = Color3.fromRGB(33, 84, 185);
		Name = "Deep blue";
		Number = 1012;
	};
	{
		Color3 = Color3.fromRGB(4, 175, 236);
		Name = "Cyan";
		Number = 1013;
	};
	{
		Color3 = Color3.fromRGB(170, 85, 0);
		Name = "CGA brown";
		Number = 1014;
	};
	{
		Color3 = Color3.fromRGB(170, 0, 170);
		Name = "Magenta";
		Number = 1015;
	};
	{
		Color3 = Color3.fromRGB(255, 102, 204);
		Name = "Pink";
		Number = 1016;
	};
	{
		Color3 = Color3.fromRGB(255, 175, 0);
		Name = "Deep orange";
		Number = 1017;
	};
	{
		Color3 = Color3.fromRGB(18, 238, 212);
		Name = "Teal";
		Number = 1018;
	};
	{
		Color3 = Color3.fromRGB(0, 255, 255);
		Name = "Toothpaste";
		Number = 1019;
	};
	{
		Color3 = Color3.fromRGB(0, 255, 0);
		Name = "Lime green";
		Number = 1020;
	};
	{
		Color3 = Color3.fromRGB(58, 125, 21);
		Name = "Camo";
		Number = 1021;
	};
	{
		Color3 = Color3.fromRGB(127, 142, 100);
		Name = "Grime";
		Number = 1022;
	};
	{
		Color3 = Color3.fromRGB(140, 91, 159);
		Name = "Lavender";
		Number = 1023;
	};
	{
		Color3 = Color3.fromRGB(175, 221, 255);
		Name = "Pastel light blue";
		Number = 1024;
	};
	{
		Color3 = Color3.fromRGB(255, 201, 201);
		Name = "Pastel orange";
		Number = 1025;
	};
	{
		Color3 = Color3.fromRGB(177, 167, 255);
		Name = "Pastel violet";
		Number = 1026;
	};
	{
		Color3 = Color3.fromRGB(159, 243, 233);
		Name = "Pastel blue-green";
		Number = 1027;
	};
	{
		Color3 = Color3.fromRGB(204, 255, 204);
		Name = "Pastel green";
		Number = 1028;
	};
	{
		Color3 = Color3.fromRGB(255, 255, 204);
		Name = "Pastel yellow";
		Number = 1029;
	};
	{
		Color3 = Color3.fromRGB(255, 204, 153);
		Name = "Pastel brown";
		Number = 1030;
	};
	{
		Color3 = Color3.fromRGB(98, 37, 209);
		Name = "Royal purple";
		Number = 1031;
	};
	{
		Color3 = Color3.fromRGB(255, 0, 191);
		Name = "Hot pink";
		Number = 1032;
	};
}

local Colors = table.freeze({
	ColorData = DeepCopy(BASE_DATA);
	SortedByName = SortedArray.new(DeepCopy(BASE_DATA), function(a, b)
		return a.Name < b.Name
	end);
	SortedByNumber = SortedArray.new(DeepCopy(BASE_DATA), function(a, b)
		return a.Number < b.Number
	end);
})

return Colors


--!optimize 2
--!strict

local Color3 = require("@contexts/Color3")
local LuauPolyfill = require("@packages/LuauPolyfill")
local TypeOf = require("@packages/TypeOf")

local Colors = require("@self/Colors")

local Error = LuauPolyfill.Error

local ColorData = Colors.ColorData
local SortedByName = Colors.SortedByName
local SortedByNumber = Colors.SortedByNumber

export type Color3 = Color3.PolyfillColor3
export type PolyfillBrickColor = typeof(setmetatable(
	{} :: {
		Color: Color3,
		Name: string,
		Number: number,

		r: number,
		g: number,
		b: number,
	},
	{} :: {
		__eq: (self: PolyfillBrickColor, other: PolyfillBrickColor) -> boolean,
		__tostring: (self: PolyfillBrickColor) -> string,
	}
))
type Static = {
	new: ((number: number) -> PolyfillBrickColor)
		& ((red: number, green: number, blue: number?) -> PolyfillBrickColor)
		& ((name: string) -> PolyfillBrickColor)
		& ((color: Color3) -> PolyfillBrickColor),
	random: () -> PolyfillBrickColor,
	palette: (number: number) -> PolyfillBrickColor,

	White: () -> PolyfillBrickColor,
	Gray: () -> PolyfillBrickColor,
	DarkGray: () -> PolyfillBrickColor,
	Black: () -> PolyfillBrickColor,
	Red: () -> PolyfillBrickColor,
	Yellow: () -> PolyfillBrickColor,
	Green: () -> PolyfillBrickColor,
	Blue: () -> PolyfillBrickColor,
}
type PrivateStatic = Static & {
	__eq: (self: PolyfillBrickColor, other: PolyfillBrickColor) -> boolean,
	__tostring: (self: PolyfillBrickColor) -> string,
}

local BrickColor = {} :: PolyfillBrickColor & Static
local Private = BrickColor :: PolyfillBrickColor & PrivateStatic
(BrickColor :: any).__index = BrickColor;
(BrickColor :: any).ClassName = "BrickColor"

local function ToBrickColor(colorData: Colors.ColorData): PolyfillBrickColor
	local color3 = colorData.Color3
	return setmetatable({
		Color = color3;
		Name = colorData.Name;
		Number = colorData.Number;

		r = color3.R / 255;
		g = color3.G / 255;
		b = color3.B / 255;
	}, BrickColor) :: never
end
local function EqualNumbers(a: Colors.ColorData, b: Colors.ColorData): boolean
	return a.Number == b.Number
end
local function EqualNames(a: Colors.ColorData, b: Colors.ColorData): boolean
	return a.Name == b.Name
end

local MEDIUM_STONE_GREY: PolyfillBrickColor = nil
local function Difference(a: Colors.ColorData, red: number, green: number, blue: number): number
	local colorA = a.Color3
	return math.abs(colorA.R - red) + math.abs(colorA.G - green) + math.abs(colorA.B - blue)
end

function BrickColor.new(...)
	local sizeOf = select("#", ...)
	if sizeOf == 2 or sizeOf == 3 then
		local red, green, maybeBlue = ...
		local blue = maybeBlue or 0

		local bestEntry: Colors.ColorData? = nil
		local bestScore = math.huge

		for _, colorData in ColorData do
			local difference = Difference(colorData, red, green, blue)
			if difference < bestScore then
				bestScore = difference
				bestEntry = colorData
			end
		end

		if not bestEntry then
			if not MEDIUM_STONE_GREY then
				MEDIUM_STONE_GREY = BrickColor.new("Medium stone grey")
			end
			return MEDIUM_STONE_GREY
		end

		return ToBrickColor(bestEntry)
	end

	local value = ...
	if type(value) == "number" then
		local index = SortedByNumber:Find({Number = value} :: never, EqualNumbers)
		if not index then
			if not MEDIUM_STONE_GREY then
				MEDIUM_STONE_GREY = BrickColor.new("Medium stone grey")
			end
			return MEDIUM_STONE_GREY
		end

		local entry = SortedByNumber.Array[index]
		if not entry then
			if not MEDIUM_STONE_GREY then
				MEDIUM_STONE_GREY = BrickColor.new("Medium stone grey")
			end
			return MEDIUM_STONE_GREY
		end

		return ToBrickColor(entry)
	end

	if type(value) == "string" then
		local index = SortedByName:Find({Name = value} :: never, EqualNames)
		if not index then
			if not MEDIUM_STONE_GREY then
				MEDIUM_STONE_GREY = BrickColor.new("Medium stone grey")
			end
			return MEDIUM_STONE_GREY
		end

		local entry = SortedByName.Array[index]
		if not entry then
			if not MEDIUM_STONE_GREY then
				MEDIUM_STONE_GREY = BrickColor.new("Medium stone grey")
			end
			return MEDIUM_STONE_GREY
		end

		return ToBrickColor(entry)
	end

	if TypeOf(value) == "Color3" then
		return BrickColor.new(value.R, value.G, value.B)
	end

	local exception = Error.new("Unknown type")
	exception.name = "UnknownTypeError"
	Error.captureStackTrace(exception, BrickColor.new)
	error(exception)
end

function BrickColor.palette()
	local exception = Error.new("No idea how this is implemented.")
	exception.name = "NotImplementedError"
	Error.captureStackTrace(exception, BrickColor.palette)
	error(exception)
end

function BrickColor.random()
	return BrickColor.new(ColorData[math.random(#ColorData)].Number)
end

function BrickColor.White()
	return BrickColor.new("White")
end

function BrickColor.Gray()
	if not MEDIUM_STONE_GREY then
		MEDIUM_STONE_GREY = BrickColor.new("Medium stone grey")
	end
	return MEDIUM_STONE_GREY
end
function BrickColor.DarkGray()
	return BrickColor.new("Dark stone grey")
end
function BrickColor.Black()
	return BrickColor.new("Black")
end
function BrickColor.Red()
	return BrickColor.new("Bright Red")
end
function BrickColor.Yellow()
	return BrickColor.new("Bright Yellow")
end
function BrickColor.Green()
	return BrickColor.new("Dark Green")
end
function BrickColor.Blue()
	return BrickColor.new("Bright Blue")
end

function Private:__eq(other)
	return self.Number == other.Number
end
function Private:__tostring()
	return self.Name
end

return table.freeze(BrickColor :: Static)