--!strict
--!optimize 2
-- Server-side transport: per-player channels, broadcast, flush.

local Players = game:GetService("Players")

local Baseline = require(script.Parent.Parent.internal.Baseline)
local Bridge = require(script.Parent.Parent.transport.Bridge)
local Channel = require(script.Parent.Parent.internal.Channel)
local Gate = require(script.Parent.Parent.transport.Gate)
local Pool = require(script.Parent.Parent.internal.Pool)
local Reader = require(script.Parent.Parent.transport.Reader)
local Types = require(script.Parent.Parent.Types)

-- State ---------------------------------------------------------------

local _reliableChannels: { [Player]: Types.ChannelState } = {}
local _unreliableChannels: { [Player]: Types.ChannelState } = {}
local _playerStats: { [Player]: Types.PlayerStats } = {}
local _statsEnabled = false

-- Private -------------------------------------------------------------

local function getReliable(player: Player): Types.ChannelState
    local ch = _reliableChannels[player]
    if not ch then
        ch = Pool.acquire()
        _reliableChannels[player] = ch
    end
    return ch
end

local function getUnreliable(player: Player): Types.ChannelState
    local ch = _unreliableChannels[player]
    if not ch then
        ch = Pool.acquire()
        _unreliableChannels[player] = ch
    end
    return ch
end

local function flushPlayer(player: Player): ()
    -- Reliable channel
    local rCh = _reliableChannels[player]
    if rCh and rCh.cursor > 0 then
        local snapshot, refs, byteLen, refCount = Channel.sealAndDump(rCh)

        -- XOR against previous frame
        local xored = Channel.xorApply(snapshot, byteLen, rCh.prevDump, rCh.prevDumpLen)
        rCh.prevDump = snapshot
        rCh.prevDumpLen = byteLen

        Bridge.fireClient(player, xored, refs)
        Channel.reset(rCh)

        if _statsEnabled then
            local ps = _playerStats[player]
            if ps then
                ps.bytesSent += byteLen
            end
        end
    end

    -- Unreliable channel (no XOR, no delta)
    local uCh = _unreliableChannels[player]
    if uCh and uCh.cursor > 0 then
        local snapshot, refs, byteLen, _ = Channel.sealAndDump(uCh)

        Bridge.fireClientUnreliable(player, snapshot, refs)
        Channel.reset(uCh)

        if _statsEnabled then
            local ps = _playerStats[player]
            if ps then
                ps.bytesSent += byteLen
            end
        end
    end
end

-- Public --------------------------------------------------------------

local Server = {}

function Server.getChannel(player: Player, isUnreliable: boolean): Types.ChannelState
    return if isUnreliable then getUnreliable(player) else getReliable(player)
end

function Server.flush(): ()
    if Channel.hasTimestamps() then
        Channel.updateTimestamp()
    end

    for player in _reliableChannels do
        flushPlayer(player)
    end

    for player in _unreliableChannels do
        if not _reliableChannels[player] then
            flushPlayer(player)
        end
    end
end

function Server.flushPlayer(player: Player): ()
    flushPlayer(player)
end

function Server.start(): ()
    local container = Instance.new("Folder")
    container.Name = "LyncRemotes"
    container.Parent = game:GetService("ReplicatedStorage")

    Bridge.setup(container)

    local reliable = Bridge.reliable()
    local unreliable = Bridge.unreliable()

    -- Incoming reliable
    reliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any?)
        local incoming = Reader.decodeIncoming(data)
        if not incoming then
            return
        end

        local incomingLen = buffer.len(incoming)

        -- Bandwidth check
        if not Gate.checkBandwidth(player, incomingLen) then
            return
        end

        -- XOR decode against player's previous outbound (client→server has no XOR)
        local ok, err = pcall(Reader.process, incoming, incomingLen, refs :: any, player, true)
        if not ok then
            warn(`[Lync] Server: read error from {player.Name}: {err}`)
        end

        if _statsEnabled then
            local ps = _playerStats[player]
            if ps then
                ps.bytesReceived += incomingLen
            end
        end
    end)

    -- Incoming unreliable
    unreliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any?)
        local incoming = Reader.decodeIncoming(data)
        if not incoming then
            return
        end

        local incomingLen = buffer.len(incoming)

        local ok, err = pcall(Reader.process, incoming, incomingLen, refs :: any, player, true)
        if not ok then
            warn(`[Lync] Server: read error (unreliable) from {player.Name}: {err}`)
        end

        if _statsEnabled then
            local ps = _playerStats[player]
            if ps then
                ps.bytesReceived += incomingLen
            end
        end
    end)

    -- Player lifecycle
    Players.PlayerAdded:Connect(function(player: Player)
        if _statsEnabled then
            _playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
        end
    end)

    Players.PlayerRemoving:Connect(function(player: Player)
        local rCh = _reliableChannels[player]
        if rCh then
            _reliableChannels[player] = nil
            Pool.release(rCh)
        end

        local uCh = _unreliableChannels[player]
        if uCh then
            _unreliableChannels[player] = nil
            Pool.release(uCh)
        end

        _playerStats[player] = nil
        Gate.clearPlayer(player)
        Baseline.clearPlayer(player)
    end)

    -- Initialize stats for existing players
    if _statsEnabled then
        for _, player in Players:GetPlayers() do
            _playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
        end
    end
end

function Server.enableStats(): ()
    _statsEnabled = true
end

function Server.getPlayerStats(player: Player): Types.PlayerStats?
    return _playerStats[player]
end

function Server.resetStats(): ()
    for player, ps in _playerStats do
        ps.bytesSent = 0
        ps.bytesReceived = 0
    end
end

return table.freeze(Server)


local colors = {
	red = function(str)
		return `\27[31m{str}\27[39m`
	end,
	green = function(str)
		return `\27[32m{str}\27[39m`
	end,
	yellow = function(str)
		return `\27[33m{str}\27[39m`
	end,
	blue = function(str)
		return `\27[34m{str}\27[39m`
	end,
	grey = function(str)
		return `\27[90m{str}\27[39m`
	end,
	bold = function(str)
		return `\27[1m{str}\27[22m`
	end,
}

return colors


local DateTime = require("@lune/datetime")
local process = require("@lune/process")

local c = require("./colors")

local generateUuid = require("./generateUuid")
local shortUuid = require("./shortUuid")
local trim = require("./trim")

export type Command = {
	label: string?,
	exec: string | (sessionId: string, args: { string }) -> (),
	args: { string }?,
	when: ((sessionId: string, isFirstRun: boolean) -> boolean)?,
}

local function createSequence(sequenceType: string, sequence: { Command })
	local isFirstRun = true

	local function run()
		local sessionId = generateUuid()

		local now = DateTime.now()
		local formattedTime = now:formatLocalTime("%X")

		local totalTasks = 0
		local tasksRun = 0

		print(
			c.yellow(
				`Running {sequenceType} sequence: {c.bold(shortUuid(sessionId))} ({formattedTime})`
			)
		)

		for _, command in sequence do
			if not command.when or command.when(sessionId, isFirstRun) then
				totalTasks += 1

				local label = command.label or tostring(command.exec)
				local isFunction = typeof(command.exec) == "function"

				local argsStr = trim(table.concat(command.args or {}, " "))
				local noun = isFunction and "function" or "command"

				if command.label then
					print(
						c.blue(`> Running {noun}:`),
						c.blue(c.bold(`{label}:`)),
						c.grey(`"{command.exec}{#argsStr > 0 and ` {argsStr}` or ""}"`)
					)
				else
					print(
						c.blue(`> Running {noun}:`),
						c.grey(`"{label}{#argsStr > 0 and ` {argsStr}` or ""}"`)
					)
				end

				local result

				if isFunction then
					local ok, err = pcall(command.exec :: any, sessionId, command.args)
					result = { ok = ok, stderr = err }
				else
					result = process.spawn(command.exec :: string, command.args)
				end

				if not result.ok then
					error(
						c.red(
							`Failed to run command: "{c.bold(command.exec)}"\n> {result.stderr}}`
						)
					)
				end

				tasksRun += 1
			end
		end

		isFirstRun = false

		return {
			isFirstRun = isFirstRun,
			sessionId = sessionId,
			totalTasks = totalTasks,
			tasksRun = tasksRun,
		}
	end

	return {
		run = run,
	}
end

return createSequence


local DateTime = require("@lune/datetime")
local fs = require("@lune/fs")
local process = require("@lune/process")
local task = require("@lune/task")

local DEFAULT_POLL_INTERVAL = 5
local SESSION_START = DateTime.now()

local function powershellGetLastModified(dir: string?)
	if not dir or not fs.isDir(dir) then
		return 0
	end

	local dirModifiedAt = (fs.metadata(dir).modifiedAt or SESSION_START) :: any
	local lastModified = dirModifiedAt.unixTimestamp

	local result = process.spawn("powershell", {
		"-Command",
		`Get-ChildItem -Recurse -Path {dir} | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | Get-Date -UFormat %s -Millisecond 0`,
	})

	if result.ok then
		local psLastModified = tonumber(result.stdout)

		if psLastModified and psLastModified > lastModified then
			lastModified = psLastModified
		end
	end

	return lastModified
end

local function watch(
	path: string?,
	callback: () -> (),
	config: {
		interval: number?,
	}?
)
	local interval = config and config.interval or DEFAULT_POLL_INTERVAL

	local lastModified = powershellGetLastModified(path)
	local thread: thread

	thread = task.spawn(function()
		while true do
			task.wait(interval)

			if not path or not fs.isDir(path) then
				if thread then
					task.cancel(thread)
				end

				return
			end

			local modifiedAt = powershellGetLastModified(path)

			if modifiedAt ~= lastModified then
				lastModified = modifiedAt
				callback()
			end
		end
	end)

	return function()
		if thread then
			task.cancel(thread)
		end
	end
end

return watch


--!strict
local function generateUuid()
	local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"

	local result = string.gsub(template, "[xy]", function(c)
		local v = (c == "x") and math.random(0, 0xf) or math.random(8, 0xb)
		return string.format("%x", v)
	end)

	return result
end

return generateUuid


--!strict
local generateUuid = require("./generateUuid")

local function shortUuid(uuid: string?)
	if uuid then
		return uuid:sub(1, 7)
	end

	return shortUuid(generateUuid())
end

return shortUuid


local function trim(str: string): string
	local trimmed = str:gsub("^%s*(.-)%s*$", "%1")
	return trimmed
end

return trim


local DateTime = require("@lune/datetime")
local fs = require("@lune/fs")
local process = require("@lune/process")
local serde = require("@lune/serde")
local task = require("@lune/task")

local c = require("./Utils/colors")
local createSequence = require("./Utils/createSequence")
local trim = require("./Utils/trim")
local watch = require("./Utils/fsWatch")

local PROJECT_JSON = serde.decode("json", fs.readFile("build.project.json"))
local REACT_PATH = "Packages/React.lua"

local WATCHED_DIRS = {
	"src",
	"packages",
}

local args = process.args

local isDev = table.find(args, "--dev") ~= nil
local isWatch = table.find(args, "--watch") ~= nil
local isReactDev = table.find(args, "--react-dev") ~= nil
local isNotReactDev = table.find(args, "--no-react-dev") ~= nil
local isSkipInstall = table.find(args, "--skip-packages") ~= nil

if isReactDev and isNotReactDev then
	error("Cannot use both `--react-dev` and `--no-react-dev` flags at the same time")
end

local removedPackages = false
local sequence = createSequence("build", {
	{
		label = "Remove packages directory",
		exec = function()
			fs.removeDir("packages")
			removedPackages = true
		end,
		when = function(_, isFirstRun)
			if isSkipInstall then
				return false
			end

			return isFirstRun and fs.isDir("packages")
		end,
	},
	{
		label = "Generate sourcemap",
		exec = "rojo",
		args = { "sourcemap", "-o", "sourcemap.json" },
		when = function()
			return removedPackages
		end,
	},
	{
		label = "Update packages",
		exec = "wally",
		args = { "install" },
		when = function(_, isFirstRun)
			if isSkipInstall then
				return false
			end

			return isFirstRun or (not isDev and not isWatch)
		end,
	},
	{
		label = "Generate sourcemap",
		exec = "rojo",
		args = { "sourcemap", "-o", "sourcemap.json" },
		when = function(_, isFirstRun)
			if isSkipInstall then
				return false
			end

			return isFirstRun or (not isDev and not isWatch)
		end,
	},
	{
		label = "Fix package types",
		exec = "wally-package-types",
		args = { "--sourcemap", "sourcemap.json", "packages" },
		when = function(_, isFirstRun)
			if isSkipInstall then
				return false
			end

			return isFirstRun or (not isDev and not isWatch)
		end,
	},
	{
		label = "Set React dev mode",
		when = function(_, isFirstRun)
			if not isFirstRun or not isReactDev or isSkipInstall then
				return false
			end

			return fs.isFile(REACT_PATH)
		end,
		exec = function()
			local reactContent = fs.readFile(REACT_PATH)

			if reactContent:find("_G.__DEV__%s*=%s*true") then
				return
			end

			reactContent =
				`-- selene: allow(global_usage)\n_G.__DEV__ = true\n\n{reactContent}`
			fs.writeFile(REACT_PATH, reactContent)
		end,
	},
	{
		label = "Force-disable React dev mode",
		when = function(_, isFirstRun)
			if not isFirstRun or not isNotReactDev or isSkipInstall then
				return false
			end

			return fs.isFile(REACT_PATH)
		end,
		exec = function()
			local reactContent = fs.readFile(REACT_PATH)

			if reactContent:find("_G.__DEV__%s*=%s*false") then
				return
			end

			reactContent =
				`-- selene: allow(global_usage)\n_G.__DEV__ = false\n\n{reactContent}`
			fs.writeFile(REACT_PATH, reactContent)
		end,
	},
	{
		label = "Remove build directory",
		exec = function()
			if fs.isDir("build") then
				fs.removeDir("build")
			end
		end,
	},
	{
		label = "Process source files",
		exec = "darklua",
		args = { "process", "src", "build", "-c", "darklua.json" },
	},
	{
		label = "Insert metadata attributes",
		exec = function(sessionId: string)
			local now = DateTime.now()

			local metadataPath = "build/init.meta.json"
			local attributes = {}

			if isDev then
				attributes.BuildSessionId = { String = sessionId }
			end

			local commitHashOp = process.spawn("git", { "rev-parse", "HEAD" })
			local commitHash: string? = commitHashOp.ok and trim(commitHashOp.stdout)
				or nil

			if commitHash and #commitHash > 0 then
				attributes.SourceCommitHash = { String = commitHash }
			end

			attributes.BuildDate = { String = now:formatLocalTime("%Y-%m-%d") }
			attributes.BuildTime = { String = now:formatLocalTime("%X") }

			local encodedMeta = serde.encode("json", {
				properties = {
					Attributes = attributes,
				},
			}, false)

			fs.writeFile(metadataPath, encodedMeta)
		end,
	},
	{
		label = "Clone localization directory",
		when = function()
			return fs.isDir("src/Frontend/Localization")
		end,
		exec = function()
			local hasLocalizationDir = fs.isDir("build/Frontend/Localization")

			for _, file in fs.readDir("src/Frontend/Localization") do
				if not file:match("%.csv$") then
					continue
				end

				if not hasLocalizationDir then
					fs.writeDir("build/Frontend/Localization")
					hasLocalizationDir = true
				end

				fs.copy(
					`src/Frontend/Localization/{file}`,
					`build/Frontend/Localization/{file}`
				)
			end
		end,
	},
	{
		label = "Strip story files",
		exec = function()
			local storyFiles = {}

			local function scanDirectory(path: string)
				for _, file in fs.readDir(path) do
					local fullPath = `{path}/{file}`
					local isDir = fs.isDir(fullPath)

					if isDir then
						scanDirectory(fullPath)
						continue
					end

					if file:match("%.story%.luau?$") then
						table.insert(storyFiles, fullPath)
					end
				end
			end

			scanDirectory("build")

			for _, file in storyFiles do
				fs.removeFile(file)
			end
		end,
		when = function()
			return not isDev
		end,
	},
	{
		label = "Build plugin to plugins directory",
		exec = "rojo",
		args = { "build", "build.project.json", "--plugin", `{PROJECT_JSON.name}.rbxm` },
		when = function()
			return isDev
		end,
	},
	{
		label = "Build plugin to file",
		exec = "rojo",
		args = { "build", "build.project.json", "-o", `{PROJECT_JSON.name}.rbxm` },
		when = function()
			return not isDev
		end,
	},
})

local function main()
	sequence.run()

	if not isWatch then
		return
	end

	local thread: thread?

	for _, dir in WATCHED_DIRS do
		watch(dir, function()
			if thread then
				task.cancel(thread)
			end

			thread = task.spawn(function()
				sequence.run()

				print(
					c.green(
						`\nWaiting for changes. Press {c.bold("CTRL+C")} to stop.\n`
					)
				)
			end)
		end, {
			interval = 2,
		})
	end

	print(c.green(`\nWaiting for changes. Press {c.bold("CTRL+C")} to stop.\n`))
end

main()


local fs = require("@lune/fs")

local c = require("./Utils/colors")
local createSequence = require("./Utils/createSequence")

type Command = {
	label: string?,
	exec: string | (sessionId: string, args: { string }) -> (),
	args: { string }?,
	when: ((sessionId: string) -> boolean)?,
}

local removedPackages = false
local sequence = createSequence("install", {
	{
		label = "Remove packages directory",
		exec = function()
			if fs.isDir("packages") then
				fs.removeDir("packages")
				removedPackages = true
			end
		end,
	},
	{
		label = "Generate sourcemap",
		exec = "rojo",
		args = { "sourcemap", "-o", "sourcemap.json" },
		when = function()
			return removedPackages
		end,
	},
	{
		label = "Install packages",
		exec = "wally",
		args = { "install" },
	},
	{
		label = "Generate sourcemap",
		exec = "rojo",
		args = { "sourcemap", "-o", "sourcemap.json" },
	},
	{
		label = "Fix package types",
		exec = "wally-package-types",
		args = { "--sourcemap", "sourcemap.json", "packages" },
	},
})

local result = sequence.run()
print(c.green(`Completed {c.bold(result.tasksRun)} tasks successfully`))


-- This file was @generated by Tarmac. It is not intended for manual editing.
return {
	Icons = {
		backspace = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(0, 0),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(0, 0),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(0, 0),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		check_circle = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(0, 196),
					ImageRectSize = Vector2.new(36, 36),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(0, 132),
					ImageRectSize = Vector2.new(24, 24),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(0, 68),
					ImageRectSize = Vector2.new(12, 12),
				}
			end
		end,
		cube = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(49, 0),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(33, 0),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(17, 0),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		fabric = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(0, 49),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(0, 33),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(0, 17),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		lock = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(98, 0),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(66, 0),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(34, 0),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		material = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(49, 49),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(33, 33),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(17, 17),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		search = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(0, 98),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(0, 66),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(0, 34),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		settings = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(147, 0),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(99, 0),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(51, 0),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		target = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(98, 49),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(66, 33),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(34, 17),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		texture = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(49, 98),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(33, 66),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(17, 34),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		view_grid = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(0, 147),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(0, 99),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(0, 51),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
		view_list = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(196, 0),
					ImageRectSize = Vector2.new(48, 48),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(132, 0),
					ImageRectSize = Vector2.new(32, 32),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(68, 0),
					ImageRectSize = Vector2.new(16, 16),
				}
			end
		end,
	},
	KeyCodeIcons = {
		command_key = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(147, 49),
					ImageRectSize = Vector2.new(42, 42),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(99, 33),
					ImageRectSize = Vector2.new(28, 28),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(51, 17),
					ImageRectSize = Vector2.new(14, 14),
				}
			end
		end,
		option_key = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(98, 98),
					ImageRectSize = Vector2.new(42, 42),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(66, 66),
					ImageRectSize = Vector2.new(28, 28),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(34, 34),
					ImageRectSize = Vector2.new(14, 14),
				}
			end
		end,
		shift_key = function(dpiScale)
			if dpiScale >= 3 then
				return {
					Image = "rbxassetid://113892098394283",
					ImageRectOffset = Vector2.new(49, 147),
					ImageRectSize = Vector2.new(42, 42),
				}
			elseif dpiScale >= 2 then
				return {
					Image = "rbxassetid://138778098643510",
					ImageRectOffset = Vector2.new(33, 99),
					ImageRectSize = Vector2.new(28, 28),
				}
			else
				return {
					Image = "rbxassetid://86839678891530",
					ImageRectOffset = Vector2.new(17, 51),
					ImageRectSize = Vector2.new(14, 14),
				}
			end
		end,
	},
	Misc = {
		arrow_up = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://17474610049"
			elseif dpiScale >= 2 then
				return "rbxassetid://17474609344"
			else
				return "rbxassetid://17474608004"
			end
		end,
		button_background = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://17636452541"
			elseif dpiScale >= 2 then
				return "rbxassetid://17636452174"
			else
				return "rbxassetid://17636451974"
			end
		end,
		button_border = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://17636452647"
			elseif dpiScale >= 2 then
				return "rbxassetid://17636452403"
			else
				return "rbxassetid://17636452074"
			end
		end,
		chevron_up = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://17425189240"
			elseif dpiScale >= 2 then
				return "rbxassetid://17425188555"
			else
				return "rbxassetid://17425186706"
			end
		end,
		circle = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://17425189333"
			elseif dpiScale >= 2 then
				return "rbxassetid://17425188692"
			else
				return "rbxassetid://17425186829"
			end
		end,
		open_new_black = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://72088669340939"
			elseif dpiScale >= 2 then
				return "rbxassetid://127311064199869"
			else
				return "rbxassetid://107873320475161"
			end
		end,
		open_new_white = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://82708698261522"
			elseif dpiScale >= 2 then
				return "rbxassetid://129328882365225"
			else
				return "rbxassetid://88523483056963"
			end
		end,
		scrollbar_bottom = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://102562334568169"
			elseif dpiScale >= 2 then
				return "rbxassetid://89880727979248"
			else
				return "rbxassetid://115322491188671"
			end
		end,
		scrollbar_middle = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://100869915138012"
			elseif dpiScale >= 2 then
				return "rbxassetid://127852681752250"
			else
				return "rbxassetid://109303061882465"
			end
		end,
		scrollbar_top = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://88488654760234"
			elseif dpiScale >= 2 then
				return "rbxassetid://101338319828821"
			else
				return "rbxassetid://71234954516539"
			end
		end,
		surface_hinge_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://72355286692240"
			elseif dpiScale >= 2 then
				return "rbxassetid://81427161370151"
			else
				return "rbxassetid://128524100674144"
			end
		end,
		surface_inlet_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://81489806969762"
			elseif dpiScale >= 2 then
				return "rbxassetid://140484853256752"
			else
				return "rbxassetid://107537997707713"
			end
		end,
		surface_motor_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://78094017580512"
			elseif dpiScale >= 2 then
				return "rbxassetid://104522696449142"
			else
				return "rbxassetid://76806182752607"
			end
		end,
		surface_smooth_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://118807181091366"
			elseif dpiScale >= 2 then
				return "rbxassetid://110593472063370"
			else
				return "rbxassetid://90575737342725"
			end
		end,
		surface_studs_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://84149269450117"
			elseif dpiScale >= 2 then
				return "rbxassetid://95393101570348"
			else
				return "rbxassetid://84836797972487"
			end
		end,
		surface_universal_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://78083330465136"
			elseif dpiScale >= 2 then
				return "rbxassetid://135481034251137"
			else
				return "rbxassetid://114250725100863"
			end
		end,
		surface_weld_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://115722506965483"
			elseif dpiScale >= 2 then
				return "rbxassetid://135174067278309"
			else
				return "rbxassetid://93285422986614"
			end
		end,
	},
	ProductIcons = {
		classic_dark_mode_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://122403614975045"
			elseif dpiScale >= 2 then
				return "rbxassetid://136243987566515"
			else
				return "rbxassetid://123752635759705"
			end
		end,
		classic_light_mode_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://104713910200707"
			elseif dpiScale >= 2 then
				return "rbxassetid://70944267113948"
			else
				return "rbxassetid://129071397894994"
			end
		end,
		dark_mode_dev_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://18638233637"
			elseif dpiScale >= 2 then
				return "rbxassetid://18638233302"
			else
				return "rbxassetid://18638232343"
			end
		end,
		dark_mode_dev_icon_512 = "rbxassetid://18638232588",
		dark_mode_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://17474610265"
			elseif dpiScale >= 2 then
				return "rbxassetid://17474609602"
			else
				return "rbxassetid://17474608218"
			end
		end,
		dark_mode_icon_512 = "rbxassetid://17474608557",
		light_mode_dev_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://18638233786"
			elseif dpiScale >= 2 then
				return "rbxassetid://18638233482"
			else
				return "rbxassetid://18638232812"
			end
		end,
		light_mode_dev_icon_512 = "rbxassetid://18638233038",
		light_mode_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://17474610411"
			elseif dpiScale >= 2 then
				return "rbxassetid://17474609782"
			else
				return "rbxassetid://17474608797"
			end
		end,
		light_mode_icon_512 = "rbxassetid://17474609049",
		mono_dark_mode_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://114737280568088"
			elseif dpiScale >= 2 then
				return "rbxassetid://140477995063714"
			else
				return "rbxassetid://117711683536506"
			end
		end,
		mono_light_mode_icon = function(dpiScale)
			if dpiScale >= 3 then
				return "rbxassetid://72388051133099"
			elseif dpiScale >= 2 then
				return "rbxassetid://113622051155526"
			else
				return "rbxassetid://121307472253145"
			end
		end,
	},
}


export type Variant = {
	surfaceType: Enum.SurfaceType,
	colorMap: string,
	normalMap: string,
	studsPerTile: number,
}

return {
	glue = {
		surfaceType = Enum.SurfaceType.Glue,
		colorMap = "rbxassetid://10509635556",
		normalMap = "rbxassetid://10509645465",
		studsPerTile = 1,
	},
	inlet = {
		surfaceType = Enum.SurfaceType.Inlet,
		colorMap = "rbxassetid://10509827175",
		normalMap = "rbxassetid://10509827228",
		studsPerTile = 1,
	},
	stud = {
		surfaceType = Enum.SurfaceType.Studs,
		colorMap = "rbxassetid://10509831729",
		normalMap = "rbxassetid://10509831753",
		studsPerTile = 1,
	},
	universal = {
		surfaceType = Enum.SurfaceType.Universal,
		colorMap = "rbxassetid://10509840768",
		normalMap = "rbxassetid://10509840812",
		studsPerTile = 2,
	},
	weld = {
		surfaceType = Enum.SurfaceType.Weld,
		colorMap = "rbxassetid://10509843977",
		normalMap = "rbxassetid://10509844001",
		studsPerTile = 2,
	},
} :: { [string]: Variant }


local React = require("@pkgs/React")

local PluginFacade = require("./PluginFacade")
local SelectionTracker = require("./Widgets/SelectionTracker")
local StudioPluginProvider = require("./Components/StudioPlugin/StudioPlugin").Provider
local ViewportWidget = require("./Widgets/ViewportWidget")

local e = React.createElement

local function App(props: {
	plugin: Plugin,
})
	return e(StudioPluginProvider, {
		plugin = props.plugin,
	}, {
		pluginFacade = e(PluginFacade),
		viewportWidget = e(ViewportWidget),
		selectionTracker = e(SelectionTracker),
	})
end

return App


local plugin: Plugin = plugin or script:FindFirstAncestorOfClass("Plugin")
local HttpService = game:GetService("HttpService")
local Studio = settings():GetService("Studio")

local IconMap = require("@root/Assets/IconMap")
local Preferences = require("@root/Stores/Preferences")

local Icon = require("./Components/Icon")

local calculateDpiScale = require("./Lib/calculateDpiScale")
local createUniqueKey = require("../Lib/createUniqueKey")
local formatEnumName = require("./Lib/formatEnumName")

local SURFACE_TYPES: { Enum.SurfaceType } = Enum.SurfaceType:GetEnumItems()

table.sort(SURFACE_TYPES, function(a, b)
	return a.Name < b.Name
end)

local SURFACE_ICONS = {
	[Enum.SurfaceType.Glue] = IconMap.Misc.surface_weld_icon,
	[Enum.SurfaceType.Hinge] = IconMap.Misc.surface_hinge_icon,
	[Enum.SurfaceType.Inlet] = IconMap.Misc.surface_inlet_icon,
	[Enum.SurfaceType.Motor] = IconMap.Misc.surface_motor_icon,
	[Enum.SurfaceType.Smooth] = IconMap.Misc.surface_smooth_icon,
	[Enum.SurfaceType.SmoothNoOutlines] = IconMap.Misc.surface_smooth_icon,
	[Enum.SurfaceType.SteppingMotor] = IconMap.Misc.surface_motor_icon,
	[Enum.SurfaceType.Studs] = IconMap.Misc.surface_studs_icon,
	[Enum.SurfaceType.Universal] = IconMap.Misc.surface_universal_icon,
	[Enum.SurfaceType.Weld] = IconMap.Misc.surface_weld_icon,
}

local additionalActions: {
	{
		name: string,
		action: (() -> ())?,
		icon: Icon.IconContent?,
	}
} =
	{
		{
			name = "Show Widget",
			-- action = function() end,
			icon = function(...)
				if Studio.Theme.Name == "Light" then
					return IconMap.Misc.open_new_black(...)
				end

				return IconMap.Misc.open_new_white(...)
			end,
		},
	}

local function showClassicMenu(callback: ((action: PluginAction?) -> ())?)
	local menuId = HttpService:GenerateGUID(false):sub(1, 7):lower()

	local baseMenu = plugin:CreatePluginMenu(menuId)
	local actions = {}

	local uniqueKey = createUniqueKey()
	local dpiScale = calculateDpiScale()

	for _, surfaceType in SURFACE_TYPES do
		local icon = SURFACE_ICONS[surfaceType](dpiScale)
		local key = `{menuId}_{uniqueKey(surfaceType.Name)}`

		local action = baseMenu:AddNewAction(key, formatEnumName(surfaceType), icon)

		action.Triggered:Connect(function()
			Preferences.selectedSurface(surfaceType)
		end)

		actions[key] = action
	end

	baseMenu:AddSeparator()

	for _, item in additionalActions do
		local key = `{menuId}_{uniqueKey(item.name)}`
		local icon: Icon.TarmacImage? = item.icon :: never

		if typeof(icon) == "function" then
			icon = icon(dpiScale)
		end

		if typeof(icon) == "string" then
			icon = { Image = icon } :: Icon.TarmacImage
		end

		local action = baseMenu:AddNewAction(key, item.name, icon and icon.Image)

		if item.action then
			action.Triggered:Connect(function()
				item.action()
			end)
		end

		actions[key] = action
	end

	local thread = task.spawn(function()
		local selectedAction = baseMenu:ShowAsync()

		if callback then
			callback(selectedAction)
		end
	end)

	return function()
		task.cancel(thread)
		baseMenu:Destroy()

		for _, action in actions do
			action:Destroy()
		end
	end
end

return {
	show = showClassicMenu,
}


local React = require("@pkgs/React")

local useDefaultProps = require("../Hooks/useDefaultProps")
local useTheme = require("../Hooks/useTheme")

local memo = React.memo
local e = React.createElement

local function ComponentGroup(props: {
	orientation: ("horizontal" | "vertical")?,
	spacing: UDim?,
	native: { [string]: any }?,
	onBoundsChanged: ((rbx: Frame) -> ())?,
	layoutNative: { [string]: any }?,
	children: React.Node?,
})
	local theme = useTheme()

	local rootElementProps = useDefaultProps(
		{
			AutomaticSize = Enum.AutomaticSize.XY,
			BackgroundTransparency = 1,
			ClipsDescendants = true,
		},
		props.native,
		{
			[React.Change.AbsoluteSize] = props.onBoundsChanged,
		}
	)

	local layoutProps = useDefaultProps({
		FillDirection = props.orientation == "vertical" and Enum.FillDirection.Vertical
			or Enum.FillDirection.Horizontal,
		Padding = props.spacing or UDim.new(0, theme.variables.spacing * 0.5),
		SortOrder = Enum.SortOrder.LayoutOrder,
		VerticalAlignment = props.orientation == "vertical"
				and Enum.VerticalAlignment.Top
			or Enum.VerticalAlignment.Center,
	}, props.layoutNative)

	return e("Frame", rootElementProps, {
		layout = e("UIListLayout", layoutProps),
	}, props.children)
end

return (memo(ComponentGroup) :: never) :: typeof(ComponentGroup)


local React = require("@pkgs/React")

local useDefaultProps = require("../Hooks/useDefaultProps")
local useTheme = require("../Hooks/useTheme")

local e = React.createElement
local memo = React.memo

local function Divider(props: {
	orientation: ("horizontal" | "vertical")?,
	native: { [string]: any }?,
})
	local theme = useTheme()
	local size = UDim2.new(1, 0, 0, theme.variables.borderWidth)

	if props.orientation == "vertical" then
		size = UDim2.fromOffset(theme.variables.borderWidth, theme.variables.textSize)
	end

	local rootElementProps = useDefaultProps({
		BackgroundColor3 = theme.colors.onSurface,
		BackgroundTransparency = 0.8,
		BorderSizePixel = 0,
		Interactable = false,
		Size = size,
	}, props.native)

	return e("Frame", rootElementProps)
end

return (memo(Divider) :: never) :: typeof(Divider)


local React = require("@pkgs/React")

local e = React.createElement

local LINE_THICKNESS = 0.15
local LINE_ZINDEX = 2

local function LeftRightWirebox(props: {
	normal: Enum.NormalId,
	part: BasePart,
	color: Color3?,
})
	if props.normal ~= Enum.NormalId.Right and props.normal ~= Enum.NormalId.Left then
		return
	end

	local part = props.part
	local size = part.Size

	local x = size.X / 2
	local y = size.Y / 2
	local z = size.Z / 2

	local side = props.normal == Enum.NormalId.Right and 1 or -1

	if part:IsA("Part") then
		if part.Shape == Enum.PartType.Ball or part.Shape == Enum.PartType.Cylinder then
			return e("CylinderHandleAdornment", {
				Color3 = props.color,
				Adornee = part,
				CFrame = CFrame.new(side * x, 0, 0)
					* CFrame.Angles(0, side * math.pi * 0.5, 0),
				Height = LINE_THICKNESS,
				InnerRadius = size.Z / 2,
				Radius = (size.Z / 2) + LINE_THICKNESS,
				ZIndex = LINE_ZINDEX,
			})
		end
	end

	local sizeY = Vector3.new(LINE_THICKNESS, size.Y + LINE_THICKNESS, LINE_THICKNESS)
	local sizeZ = Vector3.new(LINE_THICKNESS, LINE_THICKNESS, size.Z + LINE_THICKNESS)

	local topSize = sizeZ
	local topCframe = CFrame.new(side * x, y, 0)
	local topVisible = true

	local leftSize = sizeY
	local leftCframe = CFrame.new(side * x, 0, side * z)
	local leftVisible = true

	local rightSize = sizeY
	local rightCframe = CFrame.new(side * x, 0, -side * z)
	local rightVisible = true

	local isWedge = part:IsA("WedgePart")
	local isCornerWedge = part:IsA("CornerWedgePart")

	if part:IsA("Part") then
		isWedge = part.Shape == Enum.PartType.Wedge
		isCornerWedge = part.Shape == Enum.PartType.CornerWedge
	end

	if isWedge then
		local hypotenuse = math.sqrt(size.Y ^ 2 + size.Z ^ 2)

		topSize =
			Vector3.new(LINE_THICKNESS, LINE_THICKNESS, hypotenuse + LINE_THICKNESS)
		topCframe = CFrame.new(side * x, 0, 0)
			* CFrame.Angles(-math.atan2(size.Y, size.Z), 0, 0)

		leftVisible = side == 1
		rightVisible = side == -1
	elseif isCornerWedge then
		topVisible = false

		if side == -1 then
			local bottomRightPoint = CFrame.new(-x, -y, z).Position
			local bottomLeftPoint = CFrame.new(-x, -y, -z).Position
			local topLeftPoint = CFrame.new(x, y, -z).Position

			local topLeftBottomLeftMidpoint = (topLeftPoint + bottomLeftPoint) / 2
			local topLeftBottomRightMidpoint = (topLeftPoint + bottomRightPoint) / 2

			leftSize = Vector3.new(
				LINE_THICKNESS,
				LINE_THICKNESS,
				(topLeftPoint - bottomLeftPoint).Magnitude + LINE_THICKNESS
			)

			leftCframe = CFrame.lookAt(topLeftBottomLeftMidpoint, topLeftPoint)

			rightSize = Vector3.new(
				LINE_THICKNESS,
				LINE_THICKNESS,
				(topLeftPoint - bottomRightPoint).Magnitude + LINE_THICKNESS
			)

			rightCframe = CFrame.lookAt(topLeftBottomRightMidpoint, topLeftPoint)
		elseif side == 1 then
			local topRightPoint = CFrame.new(x, y, -z).Position
			local bottomLeftPoint = CFrame.new(x, -y, z).Position

			local topRightBottomLeftMidpoint = (topRightPoint + bottomLeftPoint) / 2

			leftSize = Vector3.new(
				LINE_THICKNESS,
				LINE_THICKNESS,
				(topRightPoint - bottomLeftPoint).Magnitude + LINE_THICKNESS
			)

			leftCframe = CFrame.lookAt(topRightBottomLeftMidpoint, topRightPoint)
		end
	end

	return e("Folder", nil, {
		top = topVisible and e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = topCframe,
			Size = topSize,
			ZIndex = LINE_ZINDEX,
		}),

		bottom = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = CFrame.new(side * x, -y, 0),
			Size = sizeZ,
			ZIndex = LINE_ZINDEX,
		}),

		left = leftVisible and e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = leftCframe,
			Size = leftSize,
			ZIndex = LINE_ZINDEX,
		}),

		right = rightVisible and e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = rightCframe,
			Size = rightSize,
			ZIndex = LINE_ZINDEX,
		}),
	})
end

local function FrontBackWirebox(props: {
	normal: Enum.NormalId,
	part: BasePart,
	color: Color3?,
})
	if props.normal ~= Enum.NormalId.Front and props.normal ~= Enum.NormalId.Back then
		return
	end

	local part = props.part

	local isWedge = part:IsA("WedgePart")
	local isCornerWedge = part:IsA("CornerWedgePart")

	if part:IsA("Part") then
		isWedge = part.Shape == Enum.PartType.Wedge
		isCornerWedge = part.Shape == Enum.PartType.CornerWedge
	end

	local size = part.Size

	local x = size.X / 2
	local y = size.Y / 2
	local z = size.Z / 2

	local side = props.normal == Enum.NormalId.Back and 1 or -1

	if part:IsA("Part") then
		if part.Shape == Enum.PartType.Ball then
			return e("CylinderHandleAdornment", {
				Color3 = props.color,
				Adornee = part,
				CFrame = CFrame.new(0, 0, side * z),
				Height = LINE_THICKNESS,
				InnerRadius = size.Y / 2,
				Radius = (size.Y / 2) + LINE_THICKNESS,
				ZIndex = LINE_ZINDEX,
			})
		end
	end

	local sizeX = Vector3.new(size.X + LINE_THICKNESS, LINE_THICKNESS, LINE_THICKNESS)
	local sizeY = Vector3.new(LINE_THICKNESS, size.Y + LINE_THICKNESS, LINE_THICKNESS)

	local topVisible = true

	local leftSize = sizeY
	local leftCframe = CFrame.new(-side * x, 0, side * z)

	local rightSize = sizeY
	local rightCframe = CFrame.new(side * x, 0, side * z)

	local topSize = sizeX
	local topCframe = CFrame.new(0, y, side * z)

	if isWedge and side == -1 then
		local topPoint = CFrame.new(0, y, z).Position
		local bottomPoint = CFrame.new(0, -y, -z).Position

		local topBottomMidpoint = (topPoint + bottomPoint) / 2

		topCframe = CFrame.new(topPoint)

		leftSize = Vector3.new(
			LINE_THICKNESS,
			LINE_THICKNESS,
			(topPoint - bottomPoint).Magnitude + LINE_THICKNESS
		)

		leftCframe = CFrame.new(x, 0, 0) * CFrame.lookAt(topBottomMidpoint, topPoint)

		rightSize = Vector3.new(
			LINE_THICKNESS,
			LINE_THICKNESS,
			(topPoint - bottomPoint).Magnitude + LINE_THICKNESS
		)

		rightCframe = CFrame.new(-x, 0, 0) * CFrame.lookAt(topBottomMidpoint, topPoint)
	elseif isCornerWedge then
		topVisible = false

		if side == 1 then
			local bottomRightPoint = CFrame.new(x, -y, z).Position
			local bottomLeftPoint = CFrame.new(-x, -y, z).Position
			local topLeftPoint = CFrame.new(x, y, -z).Position

			local topLeftBottomLeftMidpoint = (topLeftPoint + bottomLeftPoint) / 2
			local topLeftBottomRightMidpoint = (topLeftPoint + bottomRightPoint) / 2

			leftSize = Vector3.new(
				LINE_THICKNESS,
				LINE_THICKNESS,
				(topLeftPoint - bottomLeftPoint).Magnitude + LINE_THICKNESS
			)

			leftCframe = CFrame.lookAt(topLeftBottomLeftMidpoint, topLeftPoint)

			rightSize = Vector3.new(
				LINE_THICKNESS,
				LINE_THICKNESS,
				(topLeftPoint - bottomRightPoint).Magnitude + LINE_THICKNESS
			)

			rightCframe = CFrame.lookAt(topLeftBottomRightMidpoint, topLeftPoint)
		elseif side == -1 then
			local topLeftPoint = CFrame.new(x, y, -z).Position
			local bottomRightPoint = CFrame.new(-x, -y, -z).Position

			local topLeftBottomRightMidpoint = (topLeftPoint + bottomRightPoint) / 2

			rightSize = Vector3.new(
				LINE_THICKNESS,
				LINE_THICKNESS,
				(topLeftPoint - bottomRightPoint).Magnitude + LINE_THICKNESS
			)

			rightCframe = CFrame.lookAt(topLeftBottomRightMidpoint, topLeftPoint)
		end
	end

	return e("Folder", nil, {
		top = topVisible and e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = topCframe,
			Size = topSize,
			ZIndex = LINE_ZINDEX,
		}),

		bottom = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = CFrame.new(0, -y, side * z),
			Size = sizeX,
			ZIndex = LINE_ZINDEX,
		}),

		left = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = leftCframe,
			Size = leftSize,
			ZIndex = LINE_ZINDEX,
		}),

		right = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = rightCframe,
			Size = rightSize,
			ZIndex = LINE_ZINDEX,
		}),
	})
end

local function TopBottomWirebox(props: {
	normal: Enum.NormalId,
	part: BasePart,
	color: Color3?,
})
	if props.normal ~= Enum.NormalId.Top and props.normal ~= Enum.NormalId.Bottom then
		return
	end

	local part = props.part

	local isWedge = part:IsA("WedgePart")
	local isCornerWedge = part:IsA("CornerWedgePart")

	if part:IsA("Part") then
		isWedge = part.Shape == Enum.PartType.Wedge
		isCornerWedge = part.Shape == Enum.PartType.CornerWedge
	end

	if props.normal == Enum.NormalId.Top and (isWedge or isCornerWedge) then
		return
	end

	local size = part.Size

	local x = size.X / 2
	local y = size.Y / 2
	local z = size.Z / 2

	local side = props.normal == Enum.NormalId.Bottom and -1 or 1

	if part:IsA("Part") then
		if part.Shape == Enum.PartType.Ball then
			return e("CylinderHandleAdornment", {
				Color3 = props.color,
				Adornee = part,
				CFrame = CFrame.new(0, side * y, 0)
					* CFrame.Angles(side * math.pi * 0.5, 0, 0),
				Height = LINE_THICKNESS,
				InnerRadius = size.Z / 2,
				Radius = (size.Z / 2) + LINE_THICKNESS,
				ZIndex = LINE_ZINDEX,
			})
		end
	end

	local sizeX = Vector3.new(size.X + LINE_THICKNESS, LINE_THICKNESS, LINE_THICKNESS)
	local sizeZ = Vector3.new(LINE_THICKNESS, LINE_THICKNESS, size.Z + LINE_THICKNESS)

	return e("Folder", nil, {
		left = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = CFrame.new(-x, side * y, 0),
			Size = sizeZ,
			ZIndex = LINE_ZINDEX,
		}),

		right = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = CFrame.new(x, side * y, 0),
			Size = sizeZ,
			ZIndex = LINE_ZINDEX,
		}),

		front = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = CFrame.new(0, side * y, z),
			Size = sizeX,
			ZIndex = LINE_ZINDEX,
		}),

		back = e("BoxHandleAdornment", {
			Adornee = part,
			Color3 = props.color,
			CFrame = CFrame.new(0, side * y, -z),
			Size = sizeX,
			ZIndex = LINE_ZINDEX,
		}),
	})
end

local function NormalGizmo(props: {
	part: BasePart?,
	normal: Enum.NormalId?,
	color: Color3?,
})
	if not props.part or not props.normal then
		return
	end

	return e("Folder", nil, {
		frontBackWirebox = e(FrontBackWirebox, {
			normal = props.normal,
			part = props.part,
			color = props.color,
		}),

		leftRightWirebox = e(LeftRightWirebox, {
			normal = props.normal,
			part = props.part,
			color = props.color,
		}),

		topBottomWirebox = e(TopBottomWirebox, {
			normal = props.normal,
			part = props.part,
			color = props.color,
		}),
	})
end

return NormalGizmo


local React = require("@pkgs/React")

local e = React.createElement

local LINE_THICKNESS = 0.15

local function BoxWireframe(props: {
	part: BasePart,
	color: Color3?,
})
	local size = props.part.Size

	local x = size.X / 2
	local y = size.Y / 2
	local z = size.Z / 2

	local sizeX = Vector3.new(size.X + LINE_THICKNESS, LINE_THICKNESS, LINE_THICKNESS)
	local sizeY = Vector3.new(LINE_THICKNESS, size.Y + LINE_THICKNESS, LINE_THICKNESS)
	local sizeZ = Vector3.new(LINE_THICKNESS, LINE_THICKNESS, size.Z + LINE_THICKNESS)

	return e("Folder", nil, {
		topOutline = e("Folder", nil, {
			posX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(0, y, z),
				Size = sizeX,
				ZIndex = 2,
			}),
			negX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(0, y, -z),
				Size = sizeX,
				ZIndex = 2,
			}),

			posZ = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(x, y, 0),
				Size = sizeZ,
				ZIndex = 2,
			}),
			negZ = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(-x, y, 0),
				Size = sizeZ,
				ZIndex = 2,
			}),
		}),

		bottomOutline = e("Folder", nil, {
			posX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(0, -y, z),
				Size = sizeX,
				ZIndex = 2,
			}),
			negX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(0, -y, -z),
				Size = sizeX,
				ZIndex = 2,
			}),

			posZ = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(x, -y, 0),
				Size = sizeZ,
				ZIndex = 2,
			}),
			negZ = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(-x, -y, 0),
				Size = sizeZ,
				ZIndex = 2,
			}),
		}),

		cornerOutline = e("Folder", nil, {
			frontPosX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(x, 0, z),
				Size = sizeY,
				ZIndex = 2,
			}),
			frontNegX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(-x, 0, z),
				Size = sizeY,
				ZIndex = 2,
			}),

			backPosX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(x, 0, -z),
				Size = sizeY,
				ZIndex = 2,
			}),
			backNegX = e("BoxHandleAdornment", {
				Adornee = props.part,
				Color3 = props.color,
				CFrame = CFrame.new(-x, 0, -z),
				Size = sizeY,
				ZIndex = 2,
			}),
		}),
	})
end

local function PartGizmo(props: {
	part: BasePart?,
	color: Color3?,
})
	local part = props.part

	if not part then
		return
	end

	return e("Folder", nil, {
		outline = e("Highlight", {
			Adornee = props.part,
			FillTransparency = 1,
			OutlineColor = props.color,
		}),

		wireframe = e(BoxWireframe, {
			part = part,
			color = props.color,
		}),
	})
end

return PartGizmo


local React = require("@pkgs/React")

local useDefaultProps = require("../Hooks/useDefaultProps")
local useDpiScale = require("../Hooks/useDpiScale")
local useTheme = require("../Hooks/useTheme")
local useMemo = React.useMemo

local e = React.createElement
local memo = React.memo

export type TarmacImage = {
	Image: string,
	ImageRectSize: Vector2?,
	ImageRectOffset: Vector2?,
}

export type IconContent = string | (dpiScale: number) -> string | TarmacImage

local function Icon(props: {
	icon: IconContent,
	color: (Color3 | unknown)?,
	dpiScale: number?,
	native: { [string]: any }?,
})
	local dpiScale = useDpiScale()
	local theme = useTheme()

	local nativeIconProps = useMemo(function()
		local iconProps = typeof(props.icon) == "function"
				and props.icon(props.dpiScale or dpiScale)
			or props.icon

		if typeof(iconProps) == "string" then
			return { Image = iconProps } :: TarmacImage
		end

		return iconProps :: TarmacImage
	end, { props.icon, props.dpiScale } :: { any })

	local rootElementProps = useDefaultProps({
		BackgroundTransparency = 1,
		BorderSizePixel = 0,
		Size = UDim2.fromOffset(theme.variables.textSize, theme.variables.textSize),
		ImageColor3 = props.color,
	}, props.native, nativeIconProps)

	return e("ImageLabel", rootElementProps)
end

return (memo(Icon) :: never) :: typeof(Icon)


local React = require("@pkgs/React")

local IconMap = require("@root/Assets/IconMap")
local LocalStore = require("@root/Stores/LocalStore")
local Preferences = require("@root/Stores/Preferences")

local BasePopover = require("./BasePopover")

local useValue = require("@root/Lib/Store").useValue
local useDpiScale = require("../Hooks/useDpiScale")
local useTheme = require("../Hooks/useTheme")
local useMemo = React.useMemo

local createNextOrder = require("../Lib/createNextOrder")
local e = React.createElement
local memo = React.memo

local POPOVER_TAIL_SIZE = Vector2.new(16, 8)

local function Popover(props: {
	anchorRef: { current: GuiBase2d? },
	visible: boolean?,
	showTail: boolean?,
	padding: number?,
	containerNative: { [string]: any }?,
	scrollingFrameRef: { current: ScrollingFrame? },
	stickyHeader: React.Node?,
	children: React.Node?,
})
	local theme = useTheme()
	local dpiScale = useDpiScale()

	local nextOrder = createNextOrder()
	local showTail = props.showTail ~= false

	local pluginActive = useValue(LocalStore.isPluginActive)
	local transparencyScale = useValue(Preferences.transparencyScale)

	local popoverHeight = useMemo(
		function()
			local height = theme.variables.popoverMaxSize.Y
			local anchorRef = props.anchorRef.current

			if anchorRef then
				local anchorBottom = anchorRef.AbsolutePosition.Y
					+ anchorRef.AbsoluteSize.Y
				local viewportHeight = workspace.CurrentCamera.ViewportSize.Y
				local tailHeight = showTail and POPOVER_TAIL_SIZE.Y or 0

				if height + anchorBottom + tailHeight > viewportHeight then
					height = viewportHeight - anchorBottom - tailHeight
				end
			end

			return height
		end,
		{
			theme.variables.popoverMaxSize,
			props.visible,
			props.showTail,
		} :: { any }
	)

	return e(BasePopover, {
		visible = if pluginActive then props.visible else false,
		anchorRef = props.anchorRef,
	}, {
		tail = showTail and e("ImageLabel", {
			AnchorPoint = Vector2.new(0.5, 0),
			BackgroundTransparency = 1,
			LayoutOrder = nextOrder(),
			Position = UDim2.fromScale(0.5, 0),
			Size = UDim2.fromOffset(POPOVER_TAIL_SIZE.X, POPOVER_TAIL_SIZE.Y),
			Image = IconMap.Misc.arrow_up(dpiScale),
			ImageColor3 = theme.colors.surface,
			ImageTransparency = theme.variables.surfaceTransparency * transparencyScale,
		}),

		container = e("TextButton", {
			AutoButtonColor = false,
			BackgroundColor3 = theme.colors.surface,
			BackgroundTransparency = theme.variables.surfaceTransparency
				* transparencyScale,
			Size = UDim2.fromOffset(theme.variables.popoverMaxSize.X, popoverHeight),
			Position = showTail and UDim2.fromOffset(0, POPOVER_TAIL_SIZE.Y),
			LayoutOrder = nextOrder(),
			Text = "",
		}, {
			corners = e("UICorner", {
				CornerRadius = theme.variables.cornerRadius
					+ UDim.new(0, props.padding or theme.variables.spacing),
			}),
			padding = e("UIPadding", {
				PaddingTop = UDim.new(0, props.padding or theme.variables.spacing),
				PaddingBottom = UDim.new(0, props.padding or theme.variables.spacing),
				PaddingLeft = UDim.new(0, props.padding or theme.variables.spacing),
				PaddingRight = UDim.new(0, props.padding or theme.variables.spacing),
			}),
		}, {
			layout = props.stickyHeader and e("UIListLayout", {
				Padding = UDim.new(0, theme.variables.spacing),
				SortOrder = Enum.SortOrder.LayoutOrder,
				VerticalFlex = Enum.UIFlexAlignment.Fill,
			}),
		}, {
			stickyHeader = props.stickyHeader and e("Frame", {
				AutomaticSize = Enum.AutomaticSize.Y,
				BackgroundTransparency = 1,
				LayoutOrder = nextOrder(),
				Size = UDim2.fromScale(1, 0),
			}, props.stickyHeader),
		}, {
			container = e("ScrollingFrame", {
				ref = props.scrollingFrameRef,
				BackgroundTransparency = 1,
				BorderSizePixel = 0,
				LayoutOrder = nextOrder(),
				Size = UDim2.fromScale(1, 1),
				AutomaticCanvasSize = Enum.AutomaticSize.Y,
				CanvasSize = UDim2.fromScale(1, 0),
				ScrollingDirection = Enum.ScrollingDirection.Y,
				ScrollBarImageColor3 = theme.colors.onSurface,
				ScrollBarThickness = theme.variables.scrollbarWidth,
				ScrollBarImageTransparency = theme.variables.scrollbarTransparency
					* transparencyScale,
				BottomImage = IconMap.Misc.scrollbar_bottom(dpiScale),
				MidImage = IconMap.Misc.scrollbar_middle(dpiScale),
				TopImage = IconMap.Misc.scrollbar_top(dpiScale),
				VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
			}, props.children),
		}),
	})
end

return (memo(Popover) :: never) :: typeof(Popover)


local React = require("@pkgs/React")

local StudioPluginContexts = require("./StudioPluginContexts")

local e = React.createElement
local useContext = React.useContext

local function StudioPluginProvider(props: {
	plugin: Plugin,
	children: React.Node?,
})
	return e(StudioPluginContexts.plugin.Provider, {
		value = props.plugin,
	}, props.children)
end

local function useStudioPlugin()
	return useContext(StudioPluginContexts.plugin)
end

return {
	Provider = StudioPluginProvider,
	usePlugin = useStudioPlugin,
}


local Plugin = plugin or script:FindFirstAncestorOfClass("Plugin") :: Plugin
local React = require("@pkgs/React")

local createContext = React.createContext

local pluginContext = createContext(Plugin)
local pluginToolbarContext = createContext((nil :: never) :: PluginToolbar)

return {
	plugin = pluginContext,
	pluginToolbar = pluginToolbarContext,
}


local HttpService = game:GetService("HttpService")

local React = require("@pkgs/React")

local Icon = require("../Icon")

local useDpiScale = require("../../Hooks/useDpiScale")
local usePlugin = require("./StudioPlugin").usePlugin
local useEffect = React.useEffect

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

local function StudioPluginMenu(props: {
	visible: boolean?,
	items: {
		{
			divider: boolean?,
		} & {
			text: string,
			icon: Icon.IconContent?,
			onClick: (() -> ())?,
		}
	}?,
})
	local plugin = usePlugin()
	local dpiScale = useDpiScale()

	useEffect(function()
		if not plugin or not props.visible or not props.items then
			return
		end

		local uniqueKey = createUniqueKey()
		local baseMenu = plugin:CreatePluginMenu(HttpService:GenerateGUID(false))
		local actions = {}

		for _, item in props.items do
			if item.divider then
				baseMenu:AddSeparator()
				continue
			end

			local icon

			if item.icon then
				local iconProps = typeof(item.icon) == "function"
						and item.icon(dpiScale)
					or item.icon

				if typeof(iconProps) == "string" then
					icon = { Image = iconProps } :: Icon.TarmacImage
				end
			end

			local action = baseMenu:AddNewAction(
				uniqueKey(item.text),
				item.text,
				icon and icon.Image or nil
			)

			if item.onClick then
				action.Triggered:Connect(function()
					item.onClick()
				end)
			end

			table.insert(actions, action)
		end

		local thread = task.spawn(function()
			baseMenu:ShowAsync()
		end)

		return function()
			task.cancel(thread)
			baseMenu:Destroy()

			for _, action in actions do
				action:Destroy()
			end
		end
	end, { props.visible })

	return
end

return StudioPluginMenu


local React = require("@pkgs/React")

local StudioPlugin = require("./StudioPlugin")
local StudioPluginContexts = require("./StudioPluginContexts")

local e = React.createElement
local useContext = React.useContext
local useRef = React.useRef
local useEffect = React.useEffect

local function StudioPluginToolbarProvider(props: {
	name: string,
	children: React.Node?,
})
	local plugin = StudioPlugin.usePlugin()
	local pluginToolbar = useRef(plugin:CreateToolbar(props.name))

	useEffect(function()
		if pluginToolbar.current then
			pluginToolbar.current.Name = `PluginToolbar<{props.name}>`
			pluginToolbar.current.Parent = plugin
		end

		return function()
			if pluginToolbar.current then
				pluginToolbar.current:Destroy()
			end
		end
	end, {})

	return e(StudioPluginContexts.pluginToolbar.Provider, {
		value = pluginToolbar.current,
	}, props.children)
end

local function useStudioPluginToolbar()
	return useContext(StudioPluginContexts.pluginToolbar)
end

return {
	Provider = StudioPluginToolbarProvider,
	usePluginToolbar = useStudioPluginToolbar,
}


local React = require("@pkgs/React")

local StudioPluginToolbar = require("./StudioPluginToolbar")

local useRef = React.useRef
local useEffect = React.useEffect

local function StudioPluginToolbarButton(props: {
	id: string,
	tooltip: string?,
	icon: string?,
	text: string?,
	enabled: boolean?,
	clickableWhenViewportHidden: boolean?,
	active: boolean?,
	onClick: () -> ()?,
})
	local pluginToolbar = StudioPluginToolbar.usePluginToolbar()
	local pluginToolbarButton = useRef(nil :: PluginToolbarButton?)

	useEffect(function()
		if pluginToolbarButton.current then
			pluginToolbarButton.current:Destroy()
			pluginToolbarButton.current = nil
		end

		local toolbarButton = pluginToolbar:CreateButton(
			props.id,
			props.tooltip or "",
			props.icon or "",
			props.text or props.id
		)

		toolbarButton.Name = `Button<{props.id}>`
		toolbarButton.Parent = pluginToolbar
		pluginToolbarButton.current = toolbarButton

		return function()
			if pluginToolbarButton.current then
				pluginToolbarButton.current:Destroy()
				pluginToolbarButton.current = nil
			end
		end
	end, { props.id })

	useEffect(function()
		if not pluginToolbarButton.current or not props.onClick then
			return
		end

		local connection = pluginToolbarButton.current.Click:Connect(props.onClick)

		return function()
			if connection and connection.Connected then
				connection:Disconnect()
			end
		end
	end, { props.id, props.onClick } :: { any })

	useEffect(function()
		if pluginToolbarButton.current then
			pluginToolbarButton.current.Icon = props.icon or ""
		end
	end, { props.icon })

	useEffect(function()
		if pluginToolbarButton.current then
			pluginToolbarButton.current.Enabled = if props.enabled
				then props.enabled
				else true
		end
	end, { props.enabled })

	useEffect(function()
		if pluginToolbarButton.current then
			pluginToolbarButton.current.ClickableWhenViewportHidden = props.clickableWhenViewportHidden
				or false
		end
	end, { props.clickableWhenViewportHidden })

	useEffect(function()
		if pluginToolbarButton.current then
			pluginToolbarButton.current:SetActive(props.active or false)
		end
	end, { props.active })

	return
end

return StudioPluginToolbarButton


local Array = require("@root/Lib/Array")
local React = require("@pkgs/React")

local SurfacePickerGridItem = require("./SurfacePickerGridItem")

local useBoolean = require("../Hooks/useBoolean")
local useTheme = require("../Hooks/useTheme")
local useEffect = React.useEffect

local createUniqueKey = require("../../Lib/createUniqueKey")
local e = React.createElement
local memo = React.memo

local function SurfacePickerGridView(props: {
	surfaceTypes: { Enum.SurfaceType },
	highlightedSurfaceType: Enum.SurfaceType?,
	scrollingFrameRef: { current: ScrollingFrame? }?,
})
	local theme = useTheme()
	local uniqueKey = createUniqueKey()

	local doesOverflowY = useBoolean(
		props.scrollingFrameRef
				and props.scrollingFrameRef.current
				and props.scrollingFrameRef.current.AbsoluteCanvasSize.Y > props.scrollingFrameRef.current.AbsoluteSize.Y
			or false
	)

	useEffect(function()
		local scrollingFrame = props.scrollingFrameRef
			and props.scrollingFrameRef.current

		if not scrollingFrame then
			return
		end

		local connection = scrollingFrame
			:GetPropertyChangedSignal("AbsoluteCanvasSize")
			:Connect(function()
				local overlowsY = scrollingFrame.AbsoluteCanvasSize.Y
					> scrollingFrame.AbsoluteSize.Y
				doesOverflowY.set(overlowsY)
			end)

		return function()
			connection:Disconnect()
		end
	end, { props.scrollingFrameRef and props.scrollingFrameRef.current })

	local gridItems = Array.map(props.surfaceTypes, function(surfaceType)
		local key = uniqueKey(surfaceType.Name)

		return e(SurfacePickerGridItem, {
			surfaceType = surfaceType,
			highlighted = props.highlightedSurfaceType == surfaceType,
			key = key,
		})
	end)

	return e(React.Fragment, nil, {
		layout = e("UIListLayout", {
			Padding = UDim.new(0, theme.variables.spacing * 0.5),
			SortOrder = Enum.SortOrder.Name,
			Wraps = true,
			FillDirection = Enum.FillDirection.Horizontal,
		}),
		padding = e("UIPadding", {
			PaddingRight = doesOverflowY.value and UDim.new(
				0,
				theme.variables.spacing + theme.variables.scrollbarWidth
			) or nil,
			PaddingTop = UDim.new(0, theme.variables.spacing * 0.5),
			PaddingBottom = UDim.new(
				0,
				theme.variables.spacing + theme.variables.borderWidth * 3
			),
		}),
	}, gridItems)
end

return (memo(SurfacePickerGridView) :: never) :: typeof(SurfacePickerGridView)


local React = require("@pkgs/React")

local LocalStore = require("@root/Stores/LocalStore")
local Preferences = require("@root/Stores/Preferences")

local SurfacePreviewIcon = require("./SurfacePreview/SurfacePreviewIcon")

local useValue = require("@root/Lib/Store").useValue
local useBoolean = require("../Hooks/useBoolean")
local useTheme = require("../Hooks/useTheme")

local createNextOrder = require("../Lib/createNextOrder")
local formatEnumName = require("../Lib/formatEnumName")
local withFont = require("../Lib/withFont")
local e = React.createElement
local memo = React.memo

local function SurfacePickerListItem(props: {
	surfaceType: Enum.SurfaceType,
	highlighted: boolean?,
})
	local theme = useTheme()

	local nextOrder = createNextOrder()

	local isHovered = useBoolean(false)
	local isSelected = useValue(Preferences.selectedSurface) == props.surfaceType

	return e("TextButton", {
		AutomaticSize = Enum.AutomaticSize.Y,
		BackgroundTransparency = 1,
		Size = UDim2.fromScale(1, 1),
		Text = "",
		ClipsDescendants = true,

		[React.Event.MouseEnter] = isHovered.enable,
		[React.Event.MouseLeave] = isHovered.disable,

		[React.Event.Activated] = function()
			Preferences.selectedSurface(props.surfaceType)
			LocalStore.currentWidget(nil)
		end,
	}, {
		padding = e("UIPadding", {
			PaddingBottom = UDim.new(0, theme.variables.borderWidth),
			PaddingLeft = UDim.new(0, theme.variables.borderWidth),
			PaddingRight = UDim.new(0, theme.variables.borderWidth),
			PaddingTop = UDim.new(0, theme.variables.borderWidth),
		}),
	}, {
		container = e("Frame", {
			AutomaticSize = Enum.AutomaticSize.Y,
			BackgroundTransparency = isSelected and 0 or 1,
			BackgroundColor3 = isSelected and theme.colors.accent.default or nil,
			BorderSizePixel = 0,
			Size = UDim2.new(1, 0, 0, 0),
		}, {
			corners = e("UICorner", {
				CornerRadius = theme.variables.cornerRadius,
			}),
			padding = e("UIPadding", {
				PaddingBottom = UDim.new(0, theme.variables.spacing * 0.5),
				PaddingLeft = UDim.new(0, theme.variables.spacing * 0.5),
				PaddingRight = UDim.new(0, theme.variables.spacing * 0.5),
				PaddingTop = UDim.new(0, theme.variables.spacing * 0.5),
			}),
			layout = e("UIListLayout", {
				FillDirection = Enum.FillDirection.Horizontal,
				HorizontalAlignment = Enum.HorizontalAlignment.Left,
				HorizontalFlex = Enum.UIFlexAlignment.Fill,
				Padding = UDim.new(0, theme.variables.spacing),
				SortOrder = Enum.SortOrder.LayoutOrder,
				VerticalAlignment = Enum.VerticalAlignment.Center,
			}),
		}, {
			stroke = (isSelected or (props.highlighted or isHovered.value))
				and e("UIStroke", {
					ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
					Color = theme.colors.accent.default,
					Thickness = theme.variables.borderWidth,
				}),
		}, {
			icon = e(SurfacePreviewIcon, {
				surfaceType = props.surfaceType,
				cornerRadius = UDim.new(0, 3),
				native = {
					LayoutOrder = nextOrder(),
				},
			}, {
				stroke = isSelected and e("UIStroke", {
					ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
					Color = theme.colors.onAccent.default,
					Thickness = theme.variables.borderWidth,
				}),
			}),

			text = e("TextLabel", {
				AutomaticSize = Enum.AutomaticSize.XY,
				BackgroundTransparency = 1,
				LayoutOrder = nextOrder(),
				FontFace = withFont(
					theme.typography.body,
					isSelected and Enum.FontWeight.Bold or Enum.FontWeight.SemiBold
				),
				TextSize = theme.variables.textSize,
				Text = formatEnumName(props.surfaceType.Name),
				TextColor3 = isSelected and theme.colors.onAccent.default
					or (props.highlighted or isHovered.value) and theme.colors.accent.default
					or theme.colors.onSurface,
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Center,
			}),
		}),
	})
end

return memo(SurfacePickerListItem)


local Array = require("@root/Lib/Array")
local React = require("@pkgs/React")

local SurfacePickerListItem = require("./SurfacePickerListItem")

local useBoolean = require("../Hooks/useBoolean")
local useTheme = require("../Hooks/useTheme")
local useEffect = React.useEffect

local createUniqueKey = require("../../Lib/createUniqueKey")
local e = React.createElement
local memo = React.memo

local function SurfacePickerListView(props: {
	surfaceTypes: { Enum.SurfaceType },
	highlightedSurfaceType: Enum.SurfaceType?,
	scrollingFrameRef: { current: ScrollingFrame? }?,
})
	local theme = useTheme()
	local uniqueKey = createUniqueKey()

	local doesOverflowY = useBoolean(
		props.scrollingFrameRef
				and props.scrollingFrameRef.current
				and props.scrollingFrameRef.current.AbsoluteCanvasSize.Y > props.scrollingFrameRef.current.AbsoluteSize.Y
			or false
	)

	useEffect(function()
		local scrollingFrame = props.scrollingFrameRef
			and props.scrollingFrameRef.current

		if not scrollingFrame then
			return
		end

		local connection = scrollingFrame
			:GetPropertyChangedSignal("AbsoluteCanvasSize")
			:Connect(function()
				local overlowsY = scrollingFrame.AbsoluteCanvasSize.Y
					> scrollingFrame.AbsoluteSize.Y
				doesOverflowY.set(overlowsY)
			end)

		return function()
			connection:Disconnect()
		end
	end, { props.scrollingFrameRef and props.scrollingFrameRef.current })

	local listItems = Array.map(props.surfaceTypes, function(surfaceType)
		local key = uniqueKey(surfaceType.Name)

		return e(SurfacePickerListItem, {
			surfaceType = surfaceType,
			highlighted = props.highlightedSurfaceType == surfaceType,
			key = key,
		})
	end)

	return e(React.Fragment, nil, {
		layout = e("UIListLayout", {
			Padding = UDim.new(0, theme.variables.spacing * 0.5),
			SortOrder = Enum.SortOrder.Name,
			HorizontalFlex = Enum.UIFlexAlignment.Fill,
		}),
		padding = e("UIPadding", {
			PaddingRight = doesOverflowY.value and UDim.new(
				0,
				theme.variables.spacing + theme.variables.scrollbarWidth
			) or nil,
			PaddingTop = UDim.new(0, theme.variables.spacing * 0.5),
		}),
	}, listItems)
end

return (memo(SurfacePickerListView) :: never) :: typeof(SurfacePickerListView)


local React = require("@pkgs/React")

local e = React.createElement

local HINGE_PART_COLOR = BrickColor.Yellow().Color
local BASE_PART_COLOR = BrickColor.Gray().Color

local function HingeMotor(props: {
	isMotor: boolean,
	cframe: CFrame?,
})
	local hingePartRef = React.createRef() :: { current: Part? }

	return e("Model", {
		PrimaryPart = hingePartRef.current,
	}, {
		base = props.isMotor and e("Part", {
			Anchored = true,
			Massless = true,
			CanTouch = false,
			CanQuery = false,
			CanCollide = false,
			Shape = Enum.PartType.Cylinder,
			BottomSurface = Enum.SurfaceType.Smooth,
			CastShadow = false,
			Color = BASE_PART_COLOR,
			Material = Enum.Material.SmoothPlastic,
			CFrame = props.cframe,
			Rotation = Vector3.new(0, 0, -90),
			Size = Vector3.new(0.3, 0.8, 0.8),
			TopSurface = Enum.SurfaceType.Smooth,
		}),

		hinge = e("Part", {
			ref = hingePartRef,
			Anchored = true,
			Massless = true,
			CanTouch = false,
			CanQuery = false,
			CanCollide = false,
			Shape = Enum.PartType.Cylinder,
			BottomSurface = Enum.SurfaceType.Smooth,
			BrickColor = BrickColor.Yellow(),
			CastShadow = false,
			Color = HINGE_PART_COLOR,
			Material = Enum.Material.SmoothPlastic,
			CFrame = props.cframe,
			Rotation = Vector3.new(0, 0, -90),
			Size = Vector3.new(1, 0.4, 0.4),
			TopSurface = Enum.SurfaceType.Smooth,
		}),
	})
end

return HingeMotor


local plugin: Plugin = script:FindFirstAncestorOfClass("Plugin")

local React = require("@pkgs/React")

local HingeMotor = require("./HingeMotor")

local useDefaultProps = require("../../Hooks/useDefaultProps")
local useMemo = React.useMemo

local createUniqueKey = require("../../../Lib/createUniqueKey")
local e = React.createElement
local memo = React.memo

local PREVIEW_CAMERA = Instance.new("Camera")
PREVIEW_CAMERA.CFrame = CFrame.fromOrientation(-math.pi / 2, 0, 0) * CFrame.new(0, 0, 5)

local PSEUDO_HINGE_PROPS = {
	[Enum.SurfaceType.Hinge] = { isMotor = false },
	[Enum.SurfaceType.Motor] = { isMotor = true },
	[Enum.SurfaceType.SteppingMotor] = { isMotor = true },
}

export type RefApi = {
	updateCursorOffset: (cursorOffset: Vector2) -> (),
	revertCursorOffset: () -> (),
}

if plugin then
	PREVIEW_CAMERA.Name = "SurfacePreviewCamera"
	PREVIEW_CAMERA.Parent = plugin
end

local function SurfacePreviewLarge(props: {
	surfaceType: Enum.SurfaceType,
	partCframe: (CFrame | any)?,
	native: { [string]: any }?,
})
	local pseudoHingeProps = PSEUDO_HINGE_PROPS[props.surfaceType]

	local rootElementProps = useDefaultProps({
		Ambient = Color3.new(),
		LightColor = Color3.new(1, 1, 1),
		LightDirection = Vector3.new(0, -3, 5),
		BackgroundColor3 = Color3.new(),
		BackgroundTransparency = 1,
		CurrentCamera = PREVIEW_CAMERA,
		Size = UDim2.fromOffset(24, 24),
	}, props.native)

	local pseudoHinges = useMemo(function()
		local children: React.Node = {}

		if not pseudoHingeProps then
			return children
		end

		local uniqueKey = createUniqueKey()

		for x = 0, 180, 90 do
			for y = 0, 180, 90 do
				local hingeTransform = CFrame.fromOrientation(
					math.rad(x - 90),
					math.rad(y),
					0
				) * CFrame.new(0, 2.5, 0) * CFrame.fromOrientation(
					0,
					0,
					math.pi / 2
				)

				local hingeCframe = hingeTransform

				if typeof(props.partCframe) == "table" then
					hingeCframe = props.partCframe:map(function(cframe: CFrame)
						return cframe * hingeTransform
					end)
				elseif typeof(props.partCframe) == "CFrame" then
					hingeCframe = props.partCframe * hingeTransform
				end

				(children :: any)[uniqueKey("pseudoHinge")] = e(HingeMotor, {
					isMotor = pseudoHingeProps.isMotor,
					cframe = hingeCframe,
				})
			end
		end

		return children
	end, { props.partCframe, props.surfaceType } :: { any })

	return e("ViewportFrame", rootElementProps, {
		aspectRatio = e("UIAspectRatioConstraint", {
			AspectRatio = 1,
			DominantAxis = Enum.DominantAxis.Height,
		}),
	}, {
		part = e("Part", {
			CastShadow = false,
			Color = Color3.new(1, 1, 1),
			Material = Enum.Material.Plastic,
			CFrame = props.partCframe
				or CFrame.fromOrientation(math.pi / 2, math.pi, 0),
			Size = Vector3.one * 5,
			Shape = Enum.PartType.Ball,
			BackSurface = props.surfaceType,
			BottomSurface = props.surfaceType,
			FrontSurface = props.surfaceType,
			LeftSurface = props.surfaceType,
			RightSurface = props.surfaceType,
			TopSurface = props.surfaceType,
		}, pseudoHinges),
	})
end

return (memo(SurfacePreviewLarge) :: never) :: typeof(SurfacePreviewLarge)


local plugin: Plugin = script:FindFirstAncestorOfClass("Plugin")

local React = require("@pkgs/React")

local HingeMotor = require("./HingeMotor")

local useDefaultProps = require("../../Hooks/useDefaultProps")
local useTheme = require("../../Hooks/useTheme")

local e = React.createElement
local memo = React.memo

local PREVIEW_CAMERA = Instance.new("Camera")
PREVIEW_CAMERA.CFrame = CFrame.fromOrientation(-math.pi / 2, 0, 0)
	* CFrame.new(0, 0, 3.5)

local COLOR_BLACK = Color3.new()
local COLOR_WHITE = Color3.new(1, 1, 1)

local PART_COLOR = COLOR_WHITE
local EMULATED_PART_COLOR = Color3.fromHex("bababa")

if plugin then
	PREVIEW_CAMERA.Name = "SurfacePreviewIconCamera"
	PREVIEW_CAMERA.Parent = plugin
end

local PSEUDO_HINGE_PROPS = {
	[Enum.SurfaceType.Hinge] = { isMotor = false },
	[Enum.SurfaceType.Motor] = { isMotor = true },
	[Enum.SurfaceType.SteppingMotor] = { isMotor = true },
}

local NO_EMULATION = {
	[Enum.SurfaceType.Smooth] = true,
	[Enum.SurfaceType.SmoothNoOutlines] = true,
}

local function SurfacePreviewIcon(props: {
	surfaceType: Enum.SurfaceType,
	cornerRadius: (UDim | false)?,
	native: { [string]: any }?,
	children: React.Node?,
})
	local pseudoHingeProps = PSEUDO_HINGE_PROPS[props.surfaceType]
	local theme = useTheme()

	local rootElementProps = useDefaultProps({
		Size = UDim2.fromOffset(24, 24),
		Ambient = COLOR_BLACK,
		LightColor = COLOR_WHITE,
		LightDirection = Vector3.new(0, -3, 5),
		BackgroundColor3 = COLOR_BLACK,
		BackgroundTransparency = 1,
		CurrentCamera = PREVIEW_CAMERA,
	}, props.native)

	if NO_EMULATION[props.surfaceType] then
		return e("Frame", {
			BackgroundColor3 = EMULATED_PART_COLOR,
			Size = rootElementProps.Size,
			LayoutOrder = rootElementProps.LayoutOrder,
		}, {
			aspectRatio = e("UIAspectRatioConstraint", {
				AspectRatio = 1,
				DominantAxis = Enum.DominantAxis.Height,
			}),
			corners = props.cornerRadius ~= false and e("UICorner", {
				CornerRadius = props.cornerRadius or theme.variables.cornerRadius,
			}),
		})
	end

	return e("ViewportFrame", rootElementProps, {
		aspectRatio = e("UIAspectRatioConstraint", {
			AspectRatio = 1,
			DominantAxis = Enum.DominantAxis.Height,
		}),
		corners = props.cornerRadius ~= false and e("UICorner", {
			CornerRadius = props.cornerRadius or theme.variables.cornerRadius,
		}),
	}, {
		part = e("Part", {
			CastShadow = false,
			Color = PART_COLOR,
			CFrame = CFrame.fromOrientation(math.pi / 2, math.pi, 0),
			Size = Vector3.one * 4,
			FrontSurface = props.surfaceType,
		}),
	}, {
		pseudoHinge = pseudoHingeProps and e(HingeMotor, {
			isMotor = pseudoHingeProps.isMotor,
			cframe = CFrame.new(0, 2, 0) * CFrame.fromOrientation(0, 0, math.pi / 2),
		}),
	}, props.children)
end

return (memo(SurfacePreviewIcon) :: never) :: typeof(SurfacePreviewIcon)


local GuiService = game:GetService("GuiService")

local React = require("@pkgs/React")

local IconMap = require("@root/Assets/IconMap")
local LocalStore = require("@root/Stores/LocalStore")
local Preferences = require("@root/Stores/Preferences")

local BasePopover = require("./BasePopover")

local useValue = require("@root/Lib/Store").useValue
local useDefaultProps = require("../Hooks/useDefaultProps")
local useImperativeHandle = React.useImperativeHandle
local useBoolean = require("../Hooks/useBoolean")
local useDpiScale = require("../Hooks/useDpiScale")
local useTheme = require("../Hooks/useTheme")
local useEffect = React.useEffect
local useMemo = React.useMemo
local useRef = React.useRef

local createNextOrder = require("../Lib/createNextOrder")
local formatKeyCode = require("../Lib/formatKeyCode")
local withFont = require("../Lib/withFont")
local e = React.createElement
local memo = React.memo

local isMacOS = GuiService.IsWindows == false

local function Tooltip(
	props: {
		title: string?,
		text: string?,
		keycode: (Enum.KeyCode | string)?, -- | "CTRL" | "CMD" | "SHIFT" | "ALT" | "OPTION")?,
		visible: boolean?,
		disabled: boolean?,
		rootRef: { current: GuiBase2d? }?,
		native: { [string]: any }?,
		children: React.Node?,
	}
)
	local isHovered = useBoolean(false)
	local rootElementRef = useRef(nil :: Frame?)

	useImperativeHandle(props.rootRef, function()
		return rootElementRef.current
	end)

	local theme = useTheme()
	local dpiScale = useDpiScale()

	local pluginActive = useValue(LocalStore.isPluginActive)
	local transparencyScale = useValue(Preferences.transparencyScale)

	local nextOrder = createNextOrder()

	local rootElementProps = useDefaultProps(
		{
			AutomaticSize = Enum.AutomaticSize.XY,
			BackgroundTransparency = 1,
		},
		props.native,
		{
			ref = rootElementRef,
			[React.Event.MouseEnter] = not props.disabled and isHovered.enable,
			[React.Event.MouseLeave] = isHovered.disable,
		}
	)

	useEffect(function()
		if not pluginActive then
			isHovered.disable()
		end
	end, { pluginActive })

	if not props.title and not props.text and not props.keycode then
		return props.children
	end

	local keycode = useMemo(function()
		local keycodeTitle = formatKeyCode(props.keycode)

		if props.keycode == "SHIFT" then
			keycodeTitle = "⇧ SHIFT"
		elseif props.keycode == "CTRL" or props.keycode == "CMD" then
			keycodeTitle = isMacOS and "⌘ CMD" or "CTRL"
		elseif props.keycode == "ALT" or props.keycode == "OPTION" then
			keycodeTitle = isMacOS and "⌥ OPT" or "ALT"
		end

		return keycodeTitle
	end, { props.keycode })

	return e("Frame", rootElementProps, {
		popover = not props.disabled and e(BasePopover, {
			visible = isHovered.value,
			anchorRef = rootElementRef :: any,
			zindex = theme.elevation.tooltip,
			interactable = false,
		}, {
			layout = e("UIListLayout", {
				Padding = UDim.new(),
				SortOrder = Enum.SortOrder.LayoutOrder,
				HorizontalAlignment = Enum.HorizontalAlignment.Center,
			}),
		}, {
			tail = e("ImageLabel", {
				BackgroundTransparency = 1,
				LayoutOrder = nextOrder(),
				Size = UDim2.fromOffset(16, 8),
				Image = IconMap.Misc.arrow_up(dpiScale),
				ImageColor3 = theme.colors.surface,
				ImageTransparency = theme.variables.surfaceTransparency,
			}),

			container = e("Frame", {
				AutomaticSize = Enum.AutomaticSize.XY,
				BackgroundColor3 = theme.colors.surface,
				BackgroundTransparency = theme.variables.surfaceTransparency
					* transparencyScale,
				BorderSizePixel = 0,
				LayoutOrder = nextOrder(),
			}, {
				corners = e("UICorner", {
					CornerRadius = theme.variables.cornerRadius
						+ UDim.new(0, theme.variables.spacing),
				}),
				padding = e("UIPadding", {
					PaddingTop = UDim.new(0, theme.variables.spacing),
					PaddingBottom = UDim.new(0, theme.variables.spacing),
					PaddingLeft = UDim.new(0, theme.variables.spacing),
					PaddingRight = UDim.new(0, theme.variables.spacing),
				}),
				layout = e("UIListLayout", {
					Padding = UDim.new(0, theme.variables.spacing * 0.5),
					SortOrder = Enum.SortOrder.LayoutOrder,
				}),
				maxSize = e("UISizeConstraint", {
					MaxSize = theme.variables.tooltipMaxSize,
				}),
			}, {
				header = (props.title or props.keycode) and e("Frame", {
					AutomaticSize = Enum.AutomaticSize.Y,
					BackgroundTransparency = 1,
					LayoutOrder = nextOrder(),
					Size = UDim2.fromScale(1, 0),
				}, {
					layout = e("UIListLayout", {
						Padding = UDim.new(0, 8),
						SortOrder = Enum.SortOrder.LayoutOrder,
						FillDirection = Enum.FillDirection.Horizontal,
						HorizontalAlignment = Enum.HorizontalAlignment.Left,
						HorizontalFlex = Enum.UIFlexAlignment.SpaceBetween,
						VerticalAlignment = Enum.VerticalAlignment.Center,
						Wraps = true,
					}),
				}, {
					title = props.title and e("TextLabel", {
						AutomaticSize = Enum.AutomaticSize.XY,
						BackgroundTransparency = 1,
						LayoutOrder = nextOrder(),
						FontFace = withFont(
							theme.typography.title,
							Enum.FontWeight.ExtraBold
						),
						TextColor3 = theme.colors.onSurface,
						TextSize = theme.variables.textSize,
						Text = props.title,
						TextWrapped = true,
						TextXAlignment = Enum.TextXAlignment.Left,
					}),

					keybind = props.keycode
						and e("TextLabel", {
							AutomaticSize = Enum.AutomaticSize.XY,
							BackgroundColor3 = theme.colors.accent.default,
							LayoutOrder = nextOrder(),
							FontFace = withFont(
								theme.typography.mono,
								Enum.FontWeight.ExtraBold
							),
							TextColor3 = theme.colors.onAccent.default,
							TextSize = theme.variables.textSize,
							Text = keycode,
							TextWrapped = true,
							TextXAlignment = Enum.TextXAlignment.Left,
						}, {
							corners = e("UICorner", {
								CornerRadius = theme.variables.cornerRadius,
							}),
							padding = e("UIPadding", {
								PaddingLeft = UDim.new(
									0,
									theme.variables.spacing * 0.5
								),
								PaddingRight = UDim.new(
									0,
									theme.variables.spacing * 0.5
								),
								PaddingTop = UDim.new(0, theme.variables.spacing * 0.5),
								PaddingBottom = UDim.new(
									0,
									theme.variables.spacing * 0.5
								),
							}),
						}),
				}),

				text = props.text and e("TextLabel", {
					AutomaticSize = Enum.AutomaticSize.XY,
					BackgroundTransparency = 1,
					LayoutOrder = nextOrder(),
					FontFace = theme.typography.body,
					TextColor3 = theme.colors.onSurface,
					TextSize = theme.variables.textSize,
					Text = props.text,
					TextWrapped = true,
					TextXAlignment = Enum.TextXAlignment.Left,
				}),
			}),
		}),
	}, props.children)
end

return (memo(Tooltip) :: never) :: typeof(Tooltip)


local React = require("@pkgs/React")

local useState = React.useState
local useCallback = React.useCallback

local function useBoolean(initialValue: boolean?)
	local value, setValue = useState(initialValue or false)

	local setEnabled = useCallback(function()
		setValue(true)
	end, {})

	local setDisabled = useCallback(function()
		setValue(false)
	end, {})

	local toggleValue = useCallback(function()
		setValue(function(currentValue)
			return not currentValue
		end)
	end, {})

	return {
		value = value,
		enable = setEnabled,
		disable = setDisabled,
		toggle = toggleValue,
		set = setValue,
	}
end

return useBoolean


local useState = require("@pkgs/React").useState
local useEvent = require("./useEvent")

local function useCurrentCamera()
	local camera, setCamera = useState(workspace.CurrentCamera)

	useEvent(workspace:GetPropertyChangedSignal("CurrentCamera"), function()
		if workspace.CurrentCamera then
			setCamera(workspace.CurrentCamera)
		end
	end)

	return camera
end

return useCurrentCamera


local useMemo = require("@pkgs/React").useMemo

local function merge(...)
	local result = {}

	for _, object in { ... } do
		if typeof(object) ~= "table" then
			continue
		end

		for key, value in object do
			result[key] = value
		end
	end

	return result
end

local function useDefaultProps(...)
	local args = { ... }

	local mergedProps = useMemo(function()
		return merge(unpack(args))
	end, { args })

	return mergedProps
end

return useDefaultProps


local useMemo = require("@pkgs/React").useMemo
local useViewport = require("./useViewport")

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

local function useDpiScale(fallback: number?)
	local viewport = useViewport()

	local dpiScale = useMemo(function()
		return calculateDpiScale(fallback)
	end, { viewport, fallback } :: { any })

	return dpiScale
end

return useDpiScale


local useEffect = require("@pkgs/React").useEffect
local useLatest = require("./useLatest")

local function useEvent<T...>(
	event: RBXScriptSignal<T...>?,
	callback: (T...) -> ()?,
	options: {
		once: boolean?,
		connected: boolean?,
	}?
)
	local callbackRef = useLatest(callback)

	local once = options and options.once or false
	local connected = if options and options.connected ~= nil
		then options.connected
		else true

	useEffect(function()
		if not event or not callback or not connected then
			return
		end

		local connection: RBXScriptConnection
		local method = once and "Once" or "Connect"

		connection = event[method](event, function(...)
			if connection.Connected and callbackRef.current then
				callbackRef.current(...)
			end
		end)

		return function()
			if connection.Connected then
				connection:Disconnect()
			end
		end
	end, { event, callback ~= nil, connected } :: { any })
end

return useEvent


local React = require("@pkgs/React")

local useMemo = React.useMemo
local useRef = React.useRef

type Predicate<T = unknown> = (a: T?, b: T) -> boolean

local function isStrictEqual(a: unknown?, b: unknown)
	return a == b
end

local function useLatest<T>(value: T, predicate: Predicate<T>?): { current: T }
	local ref = useRef(value)

	useMemo(function()
		local predicateFn: Predicate<T> = predicate or isStrictEqual

		if not predicateFn(ref.current, value) then
			ref.current = value
		end
	end, { value })

	return ref :: { current: T }
end

return useLatest


local Studio = settings():GetService("Studio")
local React = require("@pkgs/React")

local useEvent = require("./useEvent")
local useState = React.useState
local useMemo = React.useMemo

local function getStudioThemeId(theme: StudioTheme)
	local studioThemeId = theme.Name:lower()
	return studioThemeId
end

local function useStudioTheme()
	local theme, setTheme = useState(Studio.Theme)

	local themeId = useMemo(function()
		return getStudioThemeId(theme)
	end, { theme })

	useEvent(Studio.ThemeChanged, function()
		setTheme(Studio.Theme)
	end)

	return theme, themeId
end

return useStudioTheme


-- local Studio = settings():GetService("Studio")

-- local React = require("@pkgs/React")

local DarkTheme = require("../Themes/Dark")

local function useTheme()
	-- local isLight, setIsLight = React.useState(Studio.Theme.Name == "Light")

	-- React.useEffect(function()
	-- 	local connection = Studio.ThemeChanged:Connect(function()
	-- 		local isLightTheme = Studio.Theme.Name == "Light"
	-- 		setIsLight(isLightTheme)
	-- 	end)

	-- 	return function()
	-- 		connection:Disconnect()
	-- 	end
	-- end, {})

	-- local matchedTheme = isLight and LightTheme or DarkTheme
	-- local resolvedTheme = matchedTheme

	-- return resolvedTheme, isLight
	return DarkTheme, false
end

return useTheme


local useState = require("@pkgs/React").useState
local useCurrentCamera = require("./useCurrentCamera")
local useEvent = require("./useEvent")

local function useViewport()
	local camera = useCurrentCamera()
	local viewport, setViewport = useState(camera.ViewportSize)

	useEvent(camera:GetPropertyChangedSignal("ViewportSize"), function()
		setViewport(camera.ViewportSize)
	end)

	return viewport
end

return useViewport


local UserInputService = game:GetService("UserInputService")

local LocalStore = require("@root/Stores/LocalStore")
local Preferences = require("@root/Stores/Preferences")

local keybindStrings = {
	CTRL = { Enum.KeyCode.LeftControl, Enum.KeyCode.RightControl },
	SHIFT = { Enum.KeyCode.LeftShift, Enum.KeyCode.RightShift },
	ALT = { Enum.KeyCode.LeftAlt, Enum.KeyCode.RightAlt },
}

local function keybindMatches(
	keybind: string | Enum.KeyCode,
	keycode: Enum.KeyCode
): boolean
	if typeof(keybind) == "EnumItem" then
		return keybind == keycode
	end

	for _, v in keybindStrings[keybind] do
		if v == keycode then
			return true
		end
	end

	return false
end

UserInputService.InputBegan:Connect(function(inputObject, robloxProcessed)
	if
		LocalStore.isListeningForKeybinds()
		or not LocalStore.isPluginActive()
		or robloxProcessed
	then
		return
	end

	local keycode = inputObject.KeyCode

	if keybindMatches(Preferences.keybindSurfacePickerWidget(), keycode) then
		LocalStore.currentWidget(function(current)
			return if current == "surfacePicker" then nil else "surfacePicker"
		end)
	elseif keybindMatches(Preferences.keybindApplyToAllFacesOverride(), keycode) then
		LocalStore.applyToAllfacesOverride(true)
	end

	if keycode == Enum.KeyCode.Escape then
		if LocalStore.currentWidget() then
			LocalStore.currentWidget(nil)
		else
			LocalStore.isPluginActive(false)
		end
	end
end)

UserInputService.InputEnded:Connect(function(inputObject)
	local keycode = inputObject.KeyCode

	if keybindMatches(Preferences.keybindApplyToAllFacesOverride(), keycode) then
		LocalStore.applyToAllfacesOverride(false)
	end
end)


local UserInputService = game:GetService("UserInputService")

local function calculateDpiScale(fallback: number?)
	local ok, scale = pcall(function()
		local buttonContentId =
			UserInputService:GetImageForKeyCode(Enum.KeyCode.ButtonA)
		local buttonScale = buttonContentId:lower():match("@([%d%.]-)x%.%w+$")

		return tonumber(buttonScale or fallback or 1)
	end)

	return ok and scale or fallback or 1
end

return calculateDpiScale


local function createNextOrder(startAt: number?)
	local layoutOrder = startAt or 0

	return function()
		layoutOrder += 1
		return layoutOrder
	end
end

return createNextOrder


local HttpService = game:GetService("HttpService")

local function createShortUuid(length: number?)
	local uuid = HttpService:GenerateGUID(false)
	return uuid:sub(1, length and math.max(1, length) or 7):lower()
end

return createShortUuid


local function formatEnumName(name: string | EnumItem)
	if typeof(name) == "EnumItem" then
		name = name.Name
	end

	local formatted = (name :: string):gsub("(%l)(%u)", "%1 %2")
	return formatted
end

return formatEnumName


local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")

local IS_MACOS = GuiService.IsWindows == false

local function formatKeyCode(keycode: (string | Enum.KeyCode)?): string?
	if not keycode then
		return
	end

	if typeof(keycode) == "string" then
		return keycode:upper()
	end

	if keycode == Enum.KeyCode.Space then
		return keycode.Name:upper()
	elseif keycode == Enum.KeyCode.Backspace then
		return "BKSP"
	elseif keycode == Enum.KeyCode.Return then
		return "ENTER"
	elseif keycode == Enum.KeyCode.Escape then
		return "ESC"
	elseif
		keycode == Enum.KeyCode.LeftControl or keycode == Enum.KeyCode.RightControl
	then
		return IS_MACOS and "CMD" or "CTRL"
	elseif keycode == Enum.KeyCode.LeftShift or keycode == Enum.KeyCode.RightShift then
		return "SHIFT"
	elseif keycode == Enum.KeyCode.LeftAlt or keycode == Enum.KeyCode.RightAlt then
		return IS_MACOS and "OPT" or "ALT"
	end

	local keyString = UserInputService:GetStringForKeyCode(keycode)

	if keyString == "" then
		return keycode.Name:upper()
	end

	return keyString:upper()
end

return formatKeyCode


local function withFont(font: Font, weight: Enum.FontWeight?, style: Enum.FontStyle?)
	if font.Weight == weight and font.Style == style then
		return font
	end

	local baseWeight = font.Weight or Enum.FontWeight.Regular
	local baseStyle = font.Style or Enum.FontStyle.Normal

	return Font.new(font.Family, weight or baseWeight, style or baseStyle)
end

return withFont


type Callback<T> = (rbx: T, input: InputObject) -> ()

local function withInputEvents(
	events: {
		hovered: ((rbx: GuiButton) -> ())?,
		pressed: ((rbx: GuiButton) -> ())?,
	},
	...: Callback<GuiButton>
)
	local callbacks = { ... }

	return function(rbx: GuiButton, input: InputObject)
		if input.UserInputType == Enum.UserInputType.MouseMovement then
			if events.hovered then
				events.hovered(rbx)
			end
		elseif input.UserInputType.Name:match("MouseButton%d") then
			if events.pressed then
				events.pressed(rbx)
			end
		end

		for _, callback in callbacks do
			callback(rbx, input)
		end
	end
end

return withInputEvents


local RuntimeScript = script.Parent.Parent

local React = require("@pkgs/React")

local IconMap = require("@root/Assets/IconMap")
local LocalStore = require("@root/Stores/LocalStore")
local Manifest = require("@root/Manifest")

local StudioPluginToolbar = require("./Components/StudioPlugin/StudioPluginToolbar")
local StudioPluginToolbarButton =
	require("./Components/StudioPlugin/StudioPluginToolbarButton")

local useValue = require("@root/Lib/Store").useValue
local useDpiScale = require("./Hooks/useDpiScale")
local useStudioTheme = require("./Hooks/useStudioTheme")
local useMemo = React.useMemo

local e = React.createElement
local memo = React.memo

local buildSessionId = RuntimeScript:GetAttribute("BuildSessionId")
local buildDate = RuntimeScript:GetAttribute("BuildDate")
local buildTime = RuntimeScript:GetAttribute("BuildTime")

local function PluginFacade()
	local pluginActive = useValue(LocalStore.isPluginActive)
	local isLegacyPluginTransition = useValue(LocalStore.isLegacyPluginTransition)

	local _, studioThemeId = useStudioTheme()
	local dpiScale = useDpiScale()

	local pluginIcon = useMemo(function()
		if buildSessionId then
			if studioThemeId == "light" then
				return IconMap.ProductIcons.light_mode_dev_icon(dpiScale)
			end

			return IconMap.ProductIcons.dark_mode_dev_icon(dpiScale)
		end

		if isLegacyPluginTransition then
			if studioThemeId == "light" then
				return IconMap.ProductIcons.classic_light_mode_icon(dpiScale)
			end

			return IconMap.ProductIcons.classic_dark_mode_icon(dpiScale)
		end

		if studioThemeId == "light" then
			return IconMap.ProductIcons.light_mode_icon(dpiScale)
		end

		return IconMap.ProductIcons.dark_mode_icon(dpiScale)
	end, { dpiScale, studioThemeId, isLegacyPluginTransition } :: { any })

	local widgetToggleButtonText = Manifest.product.display_name
	local widgetToggleButtonTooltip = Manifest.product.description

	if buildSessionId then
		widgetToggleButtonText ..= ` (#{buildSessionId:sub(1, 7):lower()})`
		widgetToggleButtonTooltip ..= `\n▶ {buildDate} @ {buildTime}`
	end

	return e(StudioPluginToolbar.Provider, {
		name = "Resurface",
	}, {
		widgetToggle = e(StudioPluginToolbarButton, {
			id = "resurface.mainWidgetToggle",
			tooltip = widgetToggleButtonTooltip,
			icon = pluginIcon,
			text = widgetToggleButtonText,
			active = pluginActive,
			onClick = function()
				LocalStore.isPluginActive(function(current)
					return not current
				end)
			end,
		}),
	})
end

return (memo(PluginFacade) :: never) :: typeof(PluginFacade)