local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
local query_parser = require(script.Parent.Parent.Parent.Parent.server.query_parser)

local create = vide.create
local source = vide.source
local show = vide.show

type props = {
	components: () -> {[string]: string},
	changes: vide.Source<{[string]: string}>,
	editing: vide.Source<false | string>,
	adding: vide.Source<false | string>,
	text: vide.Source<string>
}

return function(props: props)

	local component_edit_text = source("")
	local adding = props.adding
	local editing = props.editing
	local text = props.text

	return create "Folder" {
		Name = "Add Component",

		show(adding, function()
			return create "Frame" {
				ZIndex = 1000,
				Size = UDim2.new(1,16, 1, 16),
				Position = UDim2.fromScale(0.5, 0.5),
				AnchorPoint = Vector2.new(0.5, 0.5),

				BackgroundColor3 = Color3.new(0, 0, 0),
				BackgroundTransparency = 0.5,

				Active = true,

				create "UIListLayout" {
					HorizontalAlignment = Enum.HorizontalAlignment.Center,
					VerticalAlignment = Enum.VerticalAlignment.Center,
					Padding = UDim.new(0, 8)
				},

				pebble.padding {
					padding = UDim.new(0, 32)
				},

				pebble.textfield {
					size = UDim2.fromOffset(200, 30),

					placeholder = "Entity",

					oninput = component_edit_text,
				},

				create "Frame" {
					Size = UDim2.new(1, 0, 0, 30),
					BackgroundTransparency = 1,
					AutomaticSize = Enum.AutomaticSize.Y,

					create "UIListLayout" {
						HorizontalFlex = Enum.UIFlexAlignment.Fill,
						FillDirection = Enum.FillDirection.Horizontal,
						Padding = UDim.new(0, 8)
					},

					pebble.button {
						size = UDim2.fromOffset(150, 30),
						text = "Edit",
						activated = function()
							adding(false)
							text(
								props.components()[component_edit_text()] or ""
							)
							editing(component_edit_text())
						end,
						disabled = function()
							local ok, node = pcall(query_parser, component_edit_text())

							if not ok then return true end
							if not node[1] then return true end
							if node[2] then return true end
							local n = node[1]
							if n.type == "Relationship" then
								if n.left.type == "Wildcard" then return true end
								if n.right.type == "Wildcard" then return true end
							end

							return false
						end,
						accent = true
					},

					pebble.button {
						size = UDim2.fromOffset(150, 30),
						text = "Cancel",
						activated = function()
							adding(false)
						end
					},
				}

			}
		end)

	}

end


local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)

local create = vide.create
local show = vide.show

type props = {
	components: () -> {[string]: string},
	changes: vide.Source<{[string]: string}>,
	editing: vide.Source<false | string>,
	text: vide.Source<string>
}

return function(props: props)

	local editing = props.editing
	local text = props.text
	local changes = props.changes

	return create "Folder" {
		Name = "Text Editor",

		show(function()
			return editing()
		end, function()
			return create "Frame" {
				ZIndex = 1000,
				Size = UDim2.new(1,16, 1, 16),
				Position = UDim2.fromScale(0.5, 0.5),
				AnchorPoint = Vector2.new(0.5, 0.5),

				BackgroundColor3 = Color3.new(0, 0, 0),
				BackgroundTransparency = 0.5,

				Active = true,

				create "UIListLayout" {
					HorizontalAlignment = Enum.HorizontalAlignment.Center,
					VerticalAlignment = Enum.VerticalAlignment.Center,
					VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly,
					Padding = UDim.new(0, 8)
				},

				pebble.padding {
					padding = UDim.new(0, 32)
				},

				pebble.typography {
					text = function()
						return `Editing {editing()}`
					end
				},

				create "Frame" {
					Size = UDim2.fromScale(1, 0),

					create "UIFlexItem" {
						FlexMode = Enum.UIFlexMode.Fill
					},

					BackgroundTransparency = 1,

					pebble.textfield {
						size = UDim2.fromScale(1, 1),
						position = UDim2.fromScale(0.5, 0.5),
						anchorpoint = Vector2.new(0.5, 0.5),

						multiline = true,
						code = true,

						text = text,
						oninput = text,
					},
				},

				create "Frame" {
					Size = UDim2.new(1, 0, 0, 30),
					BackgroundTransparency = 1,
					AutomaticSize = Enum.AutomaticSize.Y,

					create "UIListLayout" {
						HorizontalFlex = Enum.UIFlexAlignment.Fill,
						FillDirection = Enum.FillDirection.Horizontal,
						Padding = UDim.new(0, 8)
					},

					pebble.button {
						size = UDim2.fromOffset(150, 30),
						text = "Save Changes",
						activated = function()
							local key = editing()
							changes()[key] = text()

							editing(false)
							changes(changes())

							if props.components()[key] ~= nil then return end
							props.components()[key] = text()
						end,
						accent = true
					},

					pebble.button {
						size = UDim2.fromOffset(150, 30),
						text = "Cancel Changes",
						activated = function()
							editing(false)
						end
					},
				}

			}
		end)

	}

end


local RunService = game:GetService("RunService")

local vide = require(script.Parent.Parent.Parent.Parent.vide)
local loop = require(script.Parent.Parent.Parent.modules.loop)
local widget = require(script.widget)

local source = vide.source
local cleanup = vide.cleanup

local overview_entity = {
	class_name = "app" :: "app",
	name = "Entity"
}

type props = {
	host: Player | "server",
	vm: number,
	id: number,
	entity: number,
}

function overview_entity.mount(props: props, destroy: () -> ())

	local keys = source({})
	local changes = source({})
	local enable_live_updates = source(true)
	local apply_changes = source(false)
	local deleting = source(false)

	-- check if the query and columns are properly
	local app_loop = loop (
		"app-client-entity",
		{
			host = props.host,
			vm = props.vm,
			id = props.id,
			entity = tonumber(props.entity),

			keys = keys,
			live_updates = enable_live_updates,
			changes = changes,
			apply_changes = apply_changes,
			deleting = deleting
		},

		{i = 1},
		script.systems.obtain_entity_data
	)

	cleanup(
		RunService.Heartbeat:Connect(app_loop)
	)

	return widget {
		host = props.host,
		vm = props.vm,
		id = props.id,
		entity = props.entity,

		components = keys,
		live_updates = enable_live_updates,
		changes = changes,
		apply_changes = apply_changes,

		delete = function()
			deleting(true)
		end,

		destroy = destroy
	}

end

return overview_entity


local vide = require(script.Parent.Parent.Parent.Parent.Parent.Parent.vide)
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)

local effect = vide.effect
local cleanup = vide.cleanup
local batch = vide.batch

type Context = {
	host: Player | "server",
	vm: number,
	id: number,
	entity: number,

	keys: vide.Source<{[string]: string}>,
	changes: vide.Source<{[string]: string}>,
	apply_changes: vide.Source<boolean>,
	live_updates: () -> boolean,
	deleting: () -> boolean,

}

local function generate_random_query_id()
	return math.random(2 ^ 31 - 1)
end

return function(context: Context)

	local inspect_entity_update = queue(remotes.inspect_entity_update)

	local current_inspectid = generate_random_query_id()
	local outgoing = {
		host = context.host,
		to_vm = context.vm,
	}

	remotes.inspect_entity:fire(
		outgoing,
		context.id,
		context.entity,
		current_inspectid
	)

	local settings_changed = false

	effect(function()
		context.live_updates()
		settings_changed = true
	end)

	cleanup(function()
		remotes.stop_inspect_entity:fire(
			outgoing,
			current_inspectid
		)
	end)

	return function()

		if context.apply_changes() then
			remotes.update_entity:fire(outgoing, current_inspectid, context.changes())
			context.apply_changes(false)
			context.changes({})
		end

		if context.deleting() then
			remotes.delete_entity:fire(outgoing, current_inspectid)
		end

		if settings_changed then
			remotes.update_inspect_settings:fire(
				outgoing,
				current_inspectid,
				{paused = not context.live_updates()}
			)
			settings_changed = false
		end

		batch(function()
			for incoming, inspectid, key, value in inspect_entity_update:iter() do
				if inspectid ~= current_inspectid then continue end
				context.keys()[key] = value
				context.keys(context.keys())
			end
		end)

	end
end


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

local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
local add_component = require(script.Parent.add_component)
local editor = require(script.Parent.editor)

local create = vide.create
local indexes = vide.indexes
local source = vide.source
local show = vide.show

type SystemId = number

type props = {
	host: Player | "server",
	vm: number,
	id: number,
	entity: number,

	components: vide.Source<{[string]: string}>,
	changes: vide.Source<{[string]: string}>,
	live_updates: vide.Source<boolean>,
	apply_changes: vide.Source<boolean>,

	destroy: () -> (),
	delete: () -> ()
}

local mouse_location = source(Vector2.zero)

RunService.PreRender:Connect(function()
	mouse_location(UserInputService:GetMouseLocation())
end)

return function(props: props)

	local live_updates = props.live_updates
	local changes = props.changes

	local text = source("")
	local adding = source(false)
	local editing = source(false :: false | string)

	local function components()
		local components = {}

		for key, value in props.components() do
			if value == "tag" then continue end
			components[key] = value
		end

		return components
	end

	local function tags()
		local tags = {}

		for key, value in props.components() do
			if value ~= "tag" then continue end
			tags[key] = value
		end

		return tags
	end

	local function is_removed(value: string)
		return value == "nil"
	end

	return pebble.widget {
		title = `Entity #{props.entity}`,
		subtitle = `host: {props.host} vm: {props.vm} id: {props.id}`,

		min_size = Vector2.new(300, 300),

		bind_to_close = props.destroy,

		create "Frame" {
			Size = UDim2.fromScale(1, 1),
			BackgroundTransparency = 1,

			create "UIListLayout" {
				VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly,
				Padding = UDim.new(0, 8)
			},

			editor {
				components = props.components,
				editing = editing,
				text = text,
				changes = changes
			},

			add_component {
				components = props.components,
				editing = editing,
				text = text,
				changes = changes,
				adding = adding
			},

			pebble.row {

				justifycontent = Enum.UIFlexAlignment.Fill,

				pebble.button {
					text = "Live Updates",
					activated = function()
						live_updates(not live_updates())
					end,

					create "UIListLayout" {
						FillDirection = Enum.FillDirection.Horizontal,
						VerticalAlignment = Enum.VerticalAlignment.Center,
						HorizontalFlex = Enum.UIFlexAlignment.SpaceBetween,
						Padding = UDim.new(0, 4)
					},

					pebble.checkbox {
						size = UDim2.fromOffset(16, 16),
						layoutorder = -1,
						checked = live_updates,

						create "UIFlexItem" {
							FlexMode = Enum.UIFlexMode.None,
						}
					}

				},

				pebble.button {
					size = UDim2.fromOffset(130, 30),
					text = function()
						local total = 0

						for _, change in changes() do
							total += 1
						end

						return `Apply {total} Edits`
					end,
					disabled = function()
						return next(changes()) == nil
					end,
					activated = function()
						props.apply_changes(true)
					end
				},

				pebble.button {
					create "UIFlexItem" {
						ItemLineAlignment = Enum.ItemLineAlignment.End,
					},
					size = UDim2.fromOffset(130, 30),

					text = "Cancel changes",
					disabled = function()
						return next(changes()) == nil
					end,
					activated = function()
						changes({})
					end
				}

			},

			create "ScrollingFrame" {
				Size = UDim2.fromScale(1, 0),
				CanvasSize = UDim2.new(),
				AutomaticCanvasSize = Enum.AutomaticSize.Y,

				BackgroundColor3 = pebble.theme.bg[-1],

				ScrollBarThickness = 6,
				VerticalScrollBarInset = Enum.ScrollBarInset.Always,

				create "UIFlexItem" {
					FlexMode = Enum.UIFlexMode.Fill
				},

				create "UIListLayout" {
					Padding = UDim.new(0, 4)
				},

				pebble.typography {text = "Components"},

				pebble.container {
					Size = UDim2.fromScale(1, 0),
					AutomaticSize = Enum.AutomaticSize.Y,

					create "UIListLayout" {
						SortOrder = Enum.SortOrder.Name,
					},

					indexes(components, function(value, key)
						return pebble.button {
							{ Name = if not string.match(key, "^%a") then "zzzz" .. key else key },
							size = UDim2.new(1, 0, 0, 32),
							automaticsize = Enum.AutomaticSize.Y,

							text = "",

							corner = false,

							activated = function()
								editing(key)
								local current_value = props.changes()[key]

								if current_value then
									text(current_value)
								else
									text(value())
								end
							end,

							create "UIListLayout" {
								FillDirection = Enum.FillDirection.Horizontal,
								HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
								VerticalAlignment = Enum.VerticalAlignment.Center,
								Padding = UDim.new(0, 8)
							},

							pebble.padding {
								y = UDim.new(0, 4)
							},

							pebble.typography {
								size = UDim2.new(0, 100, 0, 18),
								automaticsize = Enum.AutomaticSize.Y,
								text = key,
								code = true,
								wrapped = true,
								truncate = Enum.TextTruncate.SplitWord,
								xalignment = Enum.TextXAlignment.Left,
							},

							pebble.typography {
								size = UDim2.fromOffset(0, 18),
								automaticsize = Enum.AutomaticSize.Y,

								yalignment = Enum.TextYAlignment.Top,
								xalignment = Enum.TextXAlignment.Left,
								text = value,
								wrapped = true,
								truncate = Enum.TextTruncate.AtEnd,
								code = true,

								create "UIFlexItem" {
									FlexMode = Enum.UIFlexMode.Fill
								},

							},

							show(function()
								return props.changes()[key] ~= nil
							end, function()
								return pebble.typography {
									text = function()
										local old = value()
										local change = props.changes()[key]

										return if old == nil then "(added)"
											elseif is_removed(change) then "(removed)"
											else "(changed)"
									end,
									disabled = true,
									textsize = 14,
								}
							end),

							create "UISizeConstraint" {
								MaxSize = Vector2.new(math.huge, 300)
							}
						}
					end),
				},

				pebble.typography {text = "Tags"},

				pebble.container {
					Size = UDim2.fromScale(1, 0),
					AutomaticSize = Enum.AutomaticSize.Y,

					create "UIListLayout" {
						SortOrder = Enum.SortOrder.Name,
					},

					indexes(tags, function(value, key)
						local function did_change()
							return changes()[key] ~= nil
						end

						return pebble.button {
							{ Name = if not string.match(key, "^%a") then "zzzz" .. key else key },
							size = UDim2.new(1, 0, 0, 24),

							text = "",

							corner = false,

							activated = function()
								if changes()[key] == "tag" then
									changes()[key] = nil
									props.components()[key] = nil

									-- notify about the change
									changes(changes())
									props.components(props.components())
								elseif changes()[key] then
									changes()[key] = nil

									-- notify about the change
									changes(changes())
								else
									changes()[key] = "nil"

									-- notify about the change
									changes(changes())
								end
							end,

							create "UIListLayout" {
								FillDirection = Enum.FillDirection.Horizontal,
								HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
								VerticalAlignment = Enum.VerticalAlignment.Center,
								Padding = UDim.new(0, 8)
							},

							pebble.padding {
								y = UDim.new(0, 4)
							},

							pebble.typography {
								size = UDim2.fromScale(0, 1),
								text = key,
								code = true,
								wrapped = true,
								truncate = Enum.TextTruncate.SplitWord,
								xalignment = Enum.TextXAlignment.Left,

								create "UIFlexItem" {
									FlexMode = Enum.UIFlexMode.Fill
								}
							},

							show(did_change, function()
								return pebble.typography {
									text = function()
										local old = value()
										local change = changes()[key]

										return if old == nil then "(added)"
											elseif is_removed(change) then "(removed)"
											else "(changed)"
									end,
									disabled = true,
									textsize = 14,
								}
							end)
						}
					end),
				}

			},

			pebble.row {

				justifycontent = Enum.UIFlexAlignment.Fill,

				pebble.button {
					size = UDim2.fromOffset(100, 30),
					text = "Delete Id",

					activated = props.delete
				},

				pebble.button {
					size = UDim2.fromOffset(200, 30),
					text = "Add Component",

					activated = function()
						adding(true)
					end
				},

			}

		}

	}

end


local RunService = game:GetService("RunService")

local vide = require(script.Parent.Parent.Parent.Parent.vide)
local loop = require(script.Parent.Parent.Parent.modules.loop)
local widget = require(script.widget)

local cleanup = vide.cleanup

local home = {
	class_name = "app" :: "app",
	name = "Home"
}

function home.mount(_: nil, destroy: () -> ())

	local servers = vide.source {} :: any
	local app_loop = loop (
		"app-client-home",
		servers,

		{i = 1},
		script.systems.get_core_data
	)

	cleanup(
		RunService.Heartbeat:Connect(app_loop)
	)

	return widget {
		servers = servers,
		destroy = destroy
	}

end

return home


local Players = game:GetService("Players")

local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
local spawn_app = require(script.Parent.Parent.Parent.spawn_app)
local overview_scheduler = require(script.Parent.Parent.overview_scheduler)
local registry = require(script.Parent.Parent.registry)

local create = vide.create
local derive = vide.derive
local source = vide.source
local values = vide.values
local show = vide.show

type props = {

	servers: () -> {
		{
			host: "server" | Player,
			vm: number,

			schedulers: () -> {
				{id: number, name: string}
			},
			worlds: () -> {
				{id: number, name: string}
			}
		}
	},

	destroy: () -> ()

}

return function(props: props)

	local selected = source(Players.LocalPlayer)

	local hosts = derive(function()
		local hosts = {}

		for _, server in props.servers() do
			local host = server.host
			hosts[host] = hosts[host] or {}
			table.insert(hosts[host], server)
		end

		hosts["all"] = props.servers()

		return hosts
	end)

	local options = derive(function()
		local options = {}

		for host, servers in hosts() do
			options[host] = if type(host) == "string" then host else `@{host.Name}`
		end

		options[Players.LocalPlayer] = "localplayer"
		options["all"] = "all"

		return options
	end)

	local function objects()
		return hosts()[selected()] or {} :: never
	end

	local function is_empty()
		local no_objects = next(objects()) == nil
		if no_objects then return "No objects found. You may not have permissions to use this." end
		-- for _, object in objects() do
		-- 	if #object.worlds() == 0 then return "No worlds found. Did you forget to set updated?" end
		-- 	if #object.schedulers() == 0 then return "No schedulers found. Did you forget to set updated?" end
		-- end
		return false
	end

	return pebble.widget {
		title = "Home",
		min_size = Vector2.new(230, 200),
		bind_to_close = props.destroy,

		pebble.container {

			create "UIListLayout" {
				Padding = UDim.new(0, 2),
				VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly,
				HorizontalFlex = Enum.UIFlexAlignment.Fill
			},

			pebble.select {
				size = UDim2.new(1, 0, 0, 32),
				options = options :: any,
				selected = selected :: any,
				update_selected = selected
			},

			create "ScrollingFrame" {
				-- Size = UDim2.fromScale(1, 1),
				CanvasSize = UDim2.new(),
				AutomaticCanvasSize = Enum.AutomaticSize.Y,
				BackgroundTransparency = 1,
				ScrollBarThickness = 6,
				HorizontalScrollBarInset = Enum.ScrollBarInset.Always,

				create "UIFlexItem" {
					FlexMode = Enum.UIFlexMode.Fill
				},

				pebble.padding {
					x = UDim.new(0, 1),
					right = UDim.new(0, 8)
				},

				create "UIListLayout" {
					FillDirection = Enum.FillDirection.Horizontal,
					HorizontalFlex = Enum.UIFlexAlignment.Fill,
					Padding = UDim.new(0, 8),
					Wraps = true
				},

				show(is_empty, function()
					return pebble.typography {
						text = is_empty(),
						xalignment = Enum.TextXAlignment.Left,
						wrapped = true
					}
				end),

				values(objects, function(value, key)

					return pebble.pane{
						name = "",
						size = UDim2.fromOffset(200, 0),
						automaticsize = Enum.AutomaticSize.Y,

						create "UIListLayout" {
							Padding = UDim.new(0, 8)
						},

						pebble.typography {
							text = `host: {value.host}\tvm id: {value.vm}`,
							wrapped = true
						},

						values(value.worlds, function(world)
							return pebble.button {
								size = UDim2.new(1, 0, 0, 30),
								text = `World: {world.name}`,

								activated = function()
									spawn_app.spawn_app(registry, {
										host = value.host,
										vm = value.vm,
										id = world.id
									})
								end
							}
						end),

						values(value.schedulers, function(scheduler)
							return pebble.button {
								size = UDim2.new(1, 0, 0, 30),
								text = `Scheduler: {scheduler.name}`,

								activated = function()
									spawn_app.spawn_app(overview_scheduler, {
										host = value.host,
										vm = value.vm,
										id = scheduler.id
									})
								end
							}
						end)
					}
				end)
			}
		}
	}

end


local RunService = game:GetService("RunService")

local vide = require(script.Parent.Parent.Parent.Parent.vide)
local loop = require(script.Parent.Parent.Parent.modules.loop)
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
local widget = require(script.widget)

local source = vide.source
local cleanup = vide.cleanup

local overview_scheduler = {
	class_name = "app" :: "app",
	name = "Scheduler"
}

type props = {
	host: Player | "server",
	vm: number,
	id: number
}

function overview_scheduler.mount(props: props, destroy: () -> ())

	local system_data = source {}
	local system_frames = source {}
	local system_ids = source {}

	local app_loop = loop (
		"app-client-scheduler",
		{
			host = props.host,
			vm = props.vm,
			id = props.id,

			system_ids = system_ids,
			system_data = system_data,
			system_frames = system_frames,
		},

		{i = 1},
		script.systems.get_scheduler_data
	)

	cleanup(
		RunService.Heartbeat:Connect(app_loop)
	)

	return widget {
		host = props.host,
		vm = props.vm,
		id = props.id,

		system_ids = system_ids,
		system_data = system_data,
		system_frames = system_frames,
		pause_system = function(system: number)
			remotes.scheduler_system_pause:fire({
				host = props.host,
				to_vm = props.vm
			}, props.id, system, not system_data()[system].paused)
		end,

		destroy = destroy
	}

end

return overview_scheduler


local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)

local create = vide.create
local indexes = vide.indexes
local derive = vide.derive

type stack_bar = {
	values: () -> {{value: number, color: Color3}},
	selected: (number) -> ()
}

return function(props: stack_bar)

	local total = derive(function()
		local total = 0
		for _, value in props.values() do
			total += value.value
		end
		return total
	end)

	return create "Frame" {
		Name = "Graph",
		Size = UDim2.new(1, 0, 0, 32),

		indexes(props.values, function(value, index)
			return create "Frame" {

				Size = function()
					return UDim2.fromScale(value().value / total(), 1)
				end,

				BackgroundColor3 = function() return value().color end,

				MouseEnter = function()
					props.selected(index)
				end,
			}
		end),

		create "UIListLayout" {
			FillDirection = Enum.FillDirection.Horizontal,
		},
	}

end


local vide = require(script.Parent.Parent.Parent.Parent.Parent.Parent.vide)
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
local types = require(script.Parent.Parent.Parent.Parent.Parent.modules.types)

local batch = vide.batch
local cleanup = vide.cleanup

type SystemId = types.SystemId
type SystemData = types.SystemData
type SystemFrame = types.SystemFrame

type context = {
	host: Player | "server",
	vm: number,
	id: number,

	system_ids: vide.Source<{[SystemId]: true}>,
	system_data: vide.Source<{[SystemId]: types.SystemData}>,
	system_frames: vide.Source<{[SystemId]: {types.SystemFrame}}>,
}

local MAX_BUFFER_SIZE = 50

return function(context: context)
	local outgoing = {
		host = context.host,
		to_vm = context.vm
	}

	remotes.request_scheduler:fire(outgoing, context.id)

	local scheduler_static_data_updated = queue(remotes.scheduler_system_static_update)
	local scheduler_frame_data_updated = queue(remotes.scheduler_system_update)

	cleanup(function()
		remotes.disconnect_scheduler:fire(outgoing, context.id)
	end)

	return function()

		batch(function()
			for incoming, scheduler, id, new_data in scheduler_static_data_updated:iter() do
				if incoming.host ~= context.host then continue end
				if incoming.from_vm ~= context.vm then continue end
				if scheduler ~= context.id then continue end

				if new_data == nil then
					context.system_ids()[id] = nil
					context.system_data()[id] = nil
					context.system_frames()[id] = nil

					context.system_ids(context.system_ids())
					context.system_data(context.system_data())
					context.system_frames(context.system_frames())
				else
					context.system_ids()[id] = true
					context.system_data()[id] = new_data
					context.system_frames()[id] = context.system_frames()[id] or {}

					context.system_ids(context.system_ids())
					context.system_data(context.system_data())
					context.system_frames(context.system_frames())
				end

			end

			for incoming, scheduler, id, f, s in scheduler_frame_data_updated:iter() do
				if incoming.host ~= context.host then continue end
				if incoming.from_vm ~= context.vm then continue end
				if scheduler ~= context.id then continue end
				if context.system_frames()[id] == nil then continue end

				-- look where to append the frame to
				local frames = context.system_frames()[id]
				local f_data = {i = f, s = s}
				local added = false

				--- since it's unreliable we have to constantly check if we arent out of order
				for i, frame in frames do
					if frame.i == f then frames[i] = f_data; continue end
					if frame.i > f then continue end
					table.insert(frames, i, f_data)
					table.remove(frames, MAX_BUFFER_SIZE + 1)
					added = true
					break
				end

				if #frames <= MAX_BUFFER_SIZE and added == false then
					table.insert(frames, f_data)
				end

				context.system_frames(context.system_frames())
			end
		end)

	end
end


local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
local convert_units = require(script.Parent.Parent.Parent.Parent.modules.convert_units)
local types = require(script.Parent.Parent.Parent.Parent.modules.types)
local spawn_app = require(script.Parent.Parent.Parent.spawn_app)
local system_widget = require(script.Parent.Parent.system)
local stack_bar = require(script.Parent.stack_bar)

local create = vide.create
local indexes = vide.indexes
local values = vide.values
local changed = vide.changed
local source = vide.source
local derive = vide.derive

type SystemId = number

type props = {
	host: Player | "server",
	vm: number,
	id: number,

	system_ids: () -> {[SystemId]: true},
	system_data: () -> {[SystemId]: types.SystemData},
	system_frames: () -> {[SystemId]: {types.SystemFrame}},

	pause_system: (SystemId) -> (),
	destroy: () -> ()

}

local function color(n: number)
	return Color3.fromHSV((0.15 * (n-1)) % 1, 1, 1)
end

local sort_by_options = {
	"Name",
	"Id",
	"Frame Time"
}

return function(props: props)

	local selected = source(0)
	local systems_query = source("")

	local sort_by = source(2)

	local max_frametime = derive(function()
		local max = 0
		for _, frames in props.system_frames() do
			local sum = 0

			for _, frame in frames do
				sum += frame.s
			end

			max = math.max(max, sum / #frames)
		end
		return max
	end)

	local map_phases_to_systems = derive(function()
		local phases: {[false | string]: {number}} = {[false] = {}}
		for id, data in props.system_data() do
			if phases[data.phase or false] == nil then phases[data.phase or false] = {} end
			table.insert(phases[data.phase or false], id)
		end
		return phases
	end)

	local function system(id: number)
		local gui_state = source(Enum.GuiState.Idle)

		local function frame_time()
			local sum = 0
			local frames = props.system_frames()[id]

			for _, frame in frames do
				sum += frame.s
			end

			return sum / #frames
		end

		local b = create "ImageButton" {
			Name = function()
				return props.system_data()[id].name
			end,
			Size = UDim2.new(1, 0, 0, 32),

			LayoutOrder = function()
				return if sort_by() == 3 then 1e9 - frame_time() * 1e8 else id
			end,

			BackgroundColor3 = function()
				return if gui_state() == Enum.GuiState.Press then
					pebble.theme.bg[-1]()
				elseif gui_state() == Enum.GuiState.Hover then
					pebble.theme.bg[6]()
				else
					pebble.theme.bg[3]()
			end,

			Visible = function()
				return not not string.match(props.system_data()[id].name, systems_query())
			end,

			changed("GuiState", gui_state),

			Activated = function()
				spawn_app.spawn_app(system_widget, {
					host = props.host,
					vm = props.vm,

					scheduler = props.id,
					system = id,
					name = props.system_data()[id].name
				})
			end,

			MouseButton2Click = function()
				props.pause_system(id)
			end,

			-- create a frame that ignores all rules!
			create "Folder" {
				create "Frame" {
					Position = UDim2.new(0, 0, 1, 4),
					AnchorPoint = Vector2.new(0, 1),
					Size = function()
						return UDim2.new(frame_time() / max_frametime(), 0, 0, 1)
					end,

					BackgroundColor3 = pebble.theme.fg_on_bg_high[0]

				}
			},

			create "UIStroke" {
				Color = pebble.theme.bg[-3]
			},

			create "UICorner" {
				CornerRadius = UDim.new(0, 4)
			},

			create "UIListLayout" {
				FillDirection = Enum.FillDirection.Horizontal,
				VerticalAlignment = Enum.VerticalAlignment.Center,
				HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
				Padding = UDim.new(0, 8)
			},

			pebble.padding {
				x = UDim.new(0, 8)
			},

			create "Frame" {
				Size = UDim2.fromOffset(16, 16),
				AnchorPoint = Vector2.new(0.5, 0.5),

				BackgroundColor3 = color(id),

				create "UICorner" {
					CornerRadius = UDim.new(1, 0)
				},
			},

			pebble.typography {
				automaticsize = Enum.AutomaticSize.None,
				text = function()
					return props.system_data()[id].name
				end,

				truncate = Enum.TextTruncate.SplitWord,
				xalignment = Enum.TextXAlignment.Left,
				disabled = function()
					return props.system_data()[id].paused
				end,

				create "UIFlexItem" {
					FlexMode = Enum.UIFlexMode.Fill,
					GrowRatio = 1,
					ShrinkRatio = 1
				}
			},

			pebble.typography {
				automaticsize = Enum.AutomaticSize.XY,
				text = function()
					local sum = 0
					local frames = props.system_frames()[id]

					for _, frame in frames do
						sum += frame.s
					end

					return `{convert_units("s", sum / #frames)}`
				end,

				xalignment = Enum.TextXAlignment.Right,

				disabled = true,

			},

		}
		return b
	end

	return pebble.widget {
		title = "Scheduler",
		subtitle = `host: {props.host} vm: {props.vm} id: {props.id}`,

		min_size = Vector2.new(200, 300),

		bind_to_close = props.destroy,

		create "Frame" {
			Name = "Elements",
			Size = UDim2.fromScale(1, 1),
			AutomaticSize = Enum.AutomaticSize.Y,

			BackgroundTransparency = 1,

			create "UIListLayout" {
				VerticalAlignment = Enum.VerticalAlignment.Bottom,
				FillDirection = Enum.FillDirection.Vertical,
				VerticalFlex = Enum.UIFlexAlignment.SpaceBetween,
				Padding = UDim.new(0, 8)
			},

			create "Frame" {
				Size = UDim2.fromScale(1, 0),
				AutomaticSize = Enum.AutomaticSize.Y,

				BackgroundTransparency = 1,

				-- create "UIFlexItem" {
				-- 	FlexMode = Enum.UIFlexMode.Custom,
				-- 	GrowRatio = 0,
				-- 	ShrinkRatio = 0
				-- },

				pebble.pane {
					name = "Overview",
					size = UDim2.fromScale(1, 0),

					create "UIListLayout" {
						FillDirection = Enum.FillDirection.Vertical
					},

					pebble.typography {
						text = function()
							local run_time = 0
							for id, frames in props.system_frames() do
								if props.system_data()[id].paused then continue end
								local sum = 0

								for _, frame in frames do
									sum += frame.s
								end

								run_time += sum / #frames
							end

							return `Run time: {convert_units("s", run_time)}`
						end
					},

					stack_bar {
						values = function()
							local v = {}

							local system_ids = props.system_ids()
							local system_frames = props.system_frames()

							for i = 1, table.maxn(system_ids) do
								if system_ids[i] == nil then continue end
								if props.system_data()[i].paused then continue end

								local sum = 0
								local frames = system_frames[i]

								for _, frame in frames do
									sum += frame.s
								end

								table.insert(v, {value = sum / #frames, color = color(i)})
							end

							return v
						end,
						selected = selected
					},

					pebble.row {
						justifycontent = Enum.UIFlexAlignment.Fill,
						pebble.button {
							text = "Pause all",

							activated = function()
								for system, data in props.system_data() do
									if data.paused then continue end
									props.pause_system(system)
								end
							end
						},

						pebble.button {
							text = "Resume all",

							activated = function()
								for system, data in props.system_data() do
									if not data.paused then continue end
									props.pause_system(system)
								end
							end
						}
					}
				},

			},

			pebble.select {
				size = UDim2.new(1, 0, 0, 30),

				options = sort_by_options,
				selected = sort_by,
				update_selected = function(new)
					-- print(new)
					sort_by(new)
				end
			},

			pebble.textfield {
				size = UDim2.new(1, 0, 0, 36),

				placeholder = "System Match",

				oninput = systems_query,
			},

			create "ScrollingFrame" {
				Name = "Systems",

				Size = UDim2.fromScale(1, 0),
				CanvasSize = UDim2.new(),
				AutomaticCanvasSize = Enum.AutomaticSize.Y,

				BackgroundTransparency = 1,

				ScrollBarThickness = 6,
				VerticalScrollBarInset = Enum.ScrollBarInset.Always,
				ScrollBarImageColor3 = pebble.theme.fg_on_bg_low[3],

				create "UIFlexItem" {
					FlexMode = Enum.UIFlexMode.Fill
				},

				create "UIListLayout" {
					FillDirection = Enum.FillDirection.Vertical,
					Padding = UDim.new(0, 8),

					SortOrder = function()
						return if sort_by() == 1 then Enum.SortOrder.Name else Enum.SortOrder.LayoutOrder
					end
				},

				pebble.padding {
					y = UDim.new(0, 1),
					x = UDim.new(0, 1)
				},

				values(function()
					return map_phases_to_systems()[false]
				end, system),

				indexes(map_phases_to_systems, function(systems, phase)
					if phase == false then return {} end
					local expanded = source(true)
					-- print(systems())
					return pebble.accordion {
						expanded = expanded,
						set_expanded = expanded,
						text = phase,

						pebble.container {
							Size = UDim2.fromScale(1, 0),

							create "UIListLayout" {
								FillDirection = Enum.FillDirection.Vertical,
								Padding = UDim.new(0, 8),

								SortOrder = function()
									return if sort_by() == 1 then Enum.SortOrder.Name else Enum.SortOrder.LayoutOrder
								end
							},

							values(systems, system)
						}
					}
				end),

			}

		}

	}

end


local ContextActionService = game:GetService("ContextActionService")
local RunService = game:GetService("RunService")

local vide = require(script.Parent.Parent.Parent.Parent.vide)
local loop = require(script.Parent.Parent.Parent.modules.loop)
local spawn_app = require(script.Parent.Parent.spawn_app)
local entity_widget = require(script.Parent.entity)
local widget = require(script.widget)

local source = vide.source
local effect = vide.effect
local cleanup = vide.cleanup

local overview_query = {
	class_name = "app" :: "app",
	name = "Query"
}

type props = {
	host: Player | "server",
	vm: number,
	id: number
}

function overview_query.mount(props: props, destroy: () -> ())

	-- the entity id
	local current_entity = source(nil)
	-- enables picking
	local enable_pick = source(false)
	-- the entity data as a string
	local entity_hovering_over = source()
	-- the part the player is hovering over
	local hovering_over = source()

	local validate_query = source("")
	local ok = source(false)
	local msg = source("")

	local query = source("")
	local columns = source({})
	local from = source(1)
	local upto = source(25)
	local total_entities = source(0)

	local paused = source(false)
	local refresh = source(false)

	-- check if the query and columns are properly
	local app_loop = loop (
		"app-client-registry",
		{
			host = props.host,
			vm = props.vm,
			id = props.id,

			enable_pick = enable_pick,
			entity_hovering_over = entity_hovering_over,
			hovering_over = hovering_over,
			set_entity = current_entity,

			columns = columns,
			query = query,
			paused = paused,
			refresh = refresh,

			total_entities = total_entities,

			from = from,
			upto = upto,

			validate_query = validate_query,
			ok = ok,
			msg = msg
		},

		{i = 1},
		script.systems.validate_query,
		script.systems.obtain_query_data,
		script.systems.send_workspace_entity,
		script.systems.highlight_workspace_entity
	)

	cleanup(RunService.Heartbeat:Connect(app_loop))

	local function open_entity_widget(_, state: Enum.UserInputState)
		local entity = current_entity()

		if state ~= Enum.UserInputState.Begin then return end
		if entity == nil then return end

		enable_pick(false)
		entity_hovering_over(nil)
		hovering_over(nil)

		spawn_app.spawn_app(entity_widget, {
			host = props.host,
			vm = props.vm,
			id = props.id,
			entity = entity
		})
	end

	effect(function()
		local picking = enable_pick()
		local key = `select entity:{props.host} {props.vm} {props.id}`

		if picking then
			ContextActionService:BindAction(key, open_entity_widget, false, Enum.UserInputType.MouseButton1)
		end

		cleanup(function()
			ContextActionService:UnbindAction(key)
		end)

	end)

	return widget {
		host = props.host,
		vm = props.vm,
		id = props.id,

		validate_query = validate_query,

		update_system_query = query,
		current_query = query,
		total_rows_per_page = source(25),
		set_rows_per_page = source(25),

		from = from,
		upto = upto,
		total_entities = total_entities,
		paused = paused,
		refresh = refresh,

		enable_pick = enable_pick,
		entity_hovering_over = entity_hovering_over,
		hovering_over = hovering_over,

		ok = ok,
		msg = msg,

		columns = columns,

		destroy = destroy
	}

end

return overview_query


local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)

type Context = {
	host: Player | "server",
	vm: number,
	id: number,

	enable_pick: () -> boolean,
	entity_hovering_over: (string) -> (),
	set_entity: (number) -> (),
	hovering_over: (BasePart) -> ()

}

return function(context: Context)

	local send_mouse_entity = queue(remotes.send_mouse_entity)

	return function()

		for incoming, id, to_highlight, entity, components in send_mouse_entity:iter() do
			if incoming.host ~= context.host then continue end
			if incoming.from_vm ~= context.vm then continue end
			if id ~= context.id then continue end
			if context.enable_pick() == false then continue end
			context.hovering_over(to_highlight)
			context.entity_hovering_over(components)
			context.set_entity(entity)
		end

	end

end


local vide = require(script.Parent.Parent.Parent.Parent.Parent.Parent.vide)
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)

local effect = vide.effect
local batch = vide.batch
local cleanup = vide.cleanup

type Context = {
	host: Player | "server",
	vm: number,
	id: number,

	columns: vide.Source<{ { any } }>,
	query: () -> string,

	paused: () -> boolean,
	refresh: vide.Source<boolean>,

	total_entities: (number) -> (),

	from: () -> number,
	upto: () -> number,

}

local function generate_random_query_id()
	return math.random(2 ^ 31 - 1)
end

return function(context: Context)

	local query_changed = false
	local page_changed = false

	effect(function()
		if #context.query() > 0 then
			query_changed = true
		end
	end)

	effect(function()
		context.from()
		context.upto()
		page_changed = true
	end)

	local current_query_id = -1
	local query_last_frame = 0
	local update_query_result = queue(remotes.update_query_result)
	local count_updated = queue(remotes.count_total_entities)

	local columns = context.columns
	local outgoing = {
		host = context.host,
		to_vm = context.vm
	}

	cleanup(function()
		remotes.disconnect_query:fire(outgoing, current_query_id)
	end)

	local should_refresh = false
	effect(function()
		if context.refresh() ~= true then return end
		context.refresh(false)
		should_refresh = true
	end)

	local paused_state = context.paused()
	local paused_updated = false
	effect(function()
		paused_updated = true
		paused_state = context.paused()
	end)

	return function()

		if query_changed then
			columns({})
			remotes.disconnect_query:fire(outgoing, current_query_id)
			current_query_id = generate_random_query_id()
			-- print("requesting new query", current_query_id)
			remotes.request_query:fire(outgoing, context.id, current_query_id, context.query())
			remotes.advance_query_page:fire(outgoing, current_query_id, context.from(), context.upto())
			query_last_frame = 0
			query_changed = false

			remotes.pause_query:fire(outgoing, current_query_id, paused_state)
		end

		if page_changed then
			remotes.advance_query_page:fire(outgoing, current_query_id, context.from(), context.upto())
			page_changed = false
		end

		for incoming, query, value in count_updated:iter() do
			if query ~= current_query_id then continue end
			context.total_entities(value)
		end

		if paused_updated then
			remotes.pause_query:fire(outgoing, current_query_id, paused_state)
			paused_updated = false
		end

		if should_refresh then
			remotes.refresh_results:fire(outgoing, current_query_id)
			should_refresh = false
		end

		batch(function()
			for incoming, query, frame, column, row, value in update_query_result:iter() do
				if query ~= current_query_id then continue end
				if frame < query_last_frame - 10 then continue end
				query_last_frame = math.max(query_last_frame, frame)

				if columns()[column] == nil then
					columns()[column] = {}
				end

				columns()[column][row] = value
				columns(columns())
			end
		end)

	end
end


local UserInputService = game:GetService("UserInputService")

local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)

type Context = {
	host: Player | "server",
	vm: number,
	id: number,

	enable_pick: () -> boolean,

}

return function(context: Context)

	if context.enable_pick() == false then return end

	local location = UserInputService:GetMouseLocation()
	local camera = workspace.CurrentCamera

	local ray = camera:ViewportPointToRay(location.X, location.Y)

	remotes.send_mouse_pointer:fire(
		{
			host = context.host,
			to_vm = context.vm
		},
		context.id,
		ray.Origin,
		ray.Direction
	)

end


local Players = game:GetService("Players")

local vide = require(script.Parent.Parent.Parent.Parent.Parent.Parent.vide)
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)

local effect = vide.effect

type Context = {
	host: Player | "server",
	vm: number,
	id: number,

	validate_query: () -> string,

	msg: (string) -> (),
	ok: (boolean) -> ()

}

return function(context: Context)

	local query_changed = false

	effect(function()
		context.validate_query()

		query_changed = true
	end)

	local n = 0
	local already_validated = false
	local MIN_DELAY_UNTIL_VALIDATE = 0

	local validate_result = queue(remotes.validate_result)

	if context.host == Players.LocalPlayer then
		MIN_DELAY_UNTIL_VALIDATE = 0.3
	elseif context.host == "server" then
		MIN_DELAY_UNTIL_VALIDATE = 0.5
	else
		MIN_DELAY_UNTIL_VALIDATE = 0.5
	end

	return function(dt)
		if query_changed then
			n = 0
			already_validated = false
			query_changed = false

			context.ok(false)
			context.msg("")
		end

		for incoming, world, query, ok, msg in validate_result:iter() do
			if incoming.host ~= context.host then continue end
			if incoming.from_vm ~= context.vm then continue end
			if world ~= context.id then continue end
			if query ~= context.validate_query() then continue end

			context.ok(ok)
			context.msg(msg or "")
		end

		n += dt
		if n < MIN_DELAY_UNTIL_VALIDATE then return end
		if already_validated then return end

		if context.validate_query() == "" then
			context.ok(false)
			context.msg("empty query")
			return
		end

		already_validated = true

		remotes.validate_query:fire(
			{
				host = context.host,
				to_vm = context.vm
			},
			context.id,
			context.validate_query()
		)

	end
end


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

local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
local tooltip = require(script.Parent.Parent.Parent.components.tooltip)
local spawn_app = require(script.Parent.Parent.Parent.spawn_app)
local entity = require(script.Parent.Parent.entity)

local create = vide.create
local effect = vide.effect
local source = vide.source
local show = vide.show

type SystemId = number

type props = {
	host: Player | "server",
	vm: number,
	id: number,

	validate_query: (string) -> (),
	ok: () -> boolean,
	msg: () -> string,

	paused: vide.Source<boolean>,
	refresh: (boolean) -> (),

	from: vide.Source<number>,
	upto: vide.Source<number>,

	update_system_query: (query: string) -> (),
	current_query: () -> string,
	total_entities: () -> number,

	enable_pick: vide.Source<boolean>,
	entity_hovering_over: () -> string,
	hovering_over: () -> BasePart,

	columns: () -> {{any}},

	destroy: () -> ()
}

local mouse_location = source(Vector2.zero)

RunService.PreRender:Connect(function()
	mouse_location(UserInputService:GetMouseLocation())
end)

return function(props: props)

	local page_input = source("1")
	local rows_input = source("25")

	local page = source(1)
	local rows = source(20)

	effect(function()
		page_input(tostring(page()))
	end)

	effect(function()
		rows_input(tostring(rows()))
	end)

	effect(function()
		local page = page()
		local rows_per_page = rows()

		local from = (page - 1) * rows_per_page + 1
		local upto = from + rows_per_page - 1

		props.from(from)
		props.upto(upto)
	end)

	local row_template = {
		Size = UDim2.new(0, 0, 0, 26),
		AutomaticSize = Enum.AutomaticSize.X
	} :: any

	return pebble.widget {
		title = "Querying",
		subtitle = `host: {props.host} vm: {props.vm} id: {props.id}`,

		min_size = Vector2.new(300, 300),

		bind_to_close = props.destroy,

		create "Frame" {

			Size = UDim2.fromScale(1, 1),
			BackgroundTransparency = 1,

			tooltip {
				transparency = 0.3,
				visible = function()
					return props.entity_hovering_over() and #props.entity_hovering_over() > 0 or false
				end,

				pebble.typography {
					automaticsize = Enum.AutomaticSize.XY,

					text = function()
						return props.entity_hovering_over() or ""
					end,
					xalignment = Enum.TextXAlignment.Left,
					wrapped = true,
					code = true,

					{ RichText = true },

					create "UIStroke" {
						Thickness = 1,
						Color = pebble.theme.bg[-5]
					}
				}
			},

			create "Highlight" {
				DepthMode = Enum.HighlightDepthMode.AlwaysOnTop,
				OutlineColor = Color3.new(1, 1, 1),
				FillColor = pebble.theme.acc[0],
				FillTransparency = 0.5,

				Adornee = props.hovering_over
			},

			create "UIListLayout" {
				VerticalFlex = Enum.UIFlexAlignment.SpaceAround,
				Padding = UDim.new(0, 8)
			},

			create "Frame" {
				Name = "Query + Pick",
				Size = UDim2.new(1, 0, 0, 30),

				BackgroundTransparency = 1,

				create "UIListLayout" {
					FillDirection = Enum.FillDirection.Horizontal,
					HorizontalFlex = Enum.UIFlexAlignment.SpaceAround,
					VerticalFlex = Enum.UIFlexAlignment.SpaceAround,
					Padding = UDim.new(0, 8)
				},

				create "Frame" {

					Name = "Query",

					Size = UDim2.fromScale(0, 1),
					BackgroundTransparency = 1,

					create "UIFlexItem" {
						FlexMode = Enum.UIFlexMode.Fill
					},

					pebble.textfield {
						size = UDim2.new(1, 0, 0, 30),
						placeholder = "Query",

						code = true,

						oninput = function(text)
							props.validate_query(text)
						end,

						enter = function(text)
							props.update_system_query(text)
						end
					},

				},

				pebble.button {
					size = UDim2.fromOffset(30, 30),

					text = "",

					accent = props.enable_pick,

					activated = function()
						props.enable_pick(not props.enable_pick())
					end,

					create "ImageLabel" {
						Size = UDim2.fromOffset(24, 24),
						Position = UDim2.fromScale(0.5, 0.5),
						AnchorPoint = Vector2.new(0.5, 0.5),

						BackgroundTransparency = 1,

						ImageColor3 = pebble.theme.fg_on_bg_high[3],

						Image = "rbxassetid://10734898355"
					},

				},

			},

			create "Frame" {
				Size = UDim2.new(1, 0, 0, 24),

				BackgroundTransparency = 1,

				Visible = function()
					return not props.ok() and #props.msg() > 0
				end,

				pebble.typography {
					text = props.msg
				},

			},

			create "Frame" {

				Size = UDim2.new(1, 0, 0, 32),

				BackgroundColor3 = pebble.theme.bg[2],
				AutomaticSize = Enum.AutomaticSize.Y,

				create "UICorner" {
					CornerRadius = UDim.new(0, 8)
				},

				create "UIListLayout" {
					FillDirection = Enum.FillDirection.Horizontal,
					Padding = UDim.new(0, 8),
					HorizontalFlex = Enum.UIFlexAlignment.SpaceAround,
					VerticalAlignment = Enum.VerticalAlignment.Center,
					Wraps = true
				},

				pebble.row {
					spacing = UDim.new(0, 8),
					alignitems = Enum.ItemLineAlignment.Center,
					row_template,

					pebble.typography {
						text = "Page:"
					},

					pebble.textfield {
						size = UDim2.fromOffset(40, 26),
						placeholder = "1",

						text = function()
							return page_input()
						end,

						oninput = page_input,

						enter = function(text)
							local n = tonumber(text)

							if n == nil then
								page_input(tostring(page()))
							else
								page(n)
							end
						end
					},

					pebble.typography {
						text = function()
							return `/ {math.ceil(props.total_entities() / rows())}`
						end
					},
				},

				pebble.row {
					spacing = UDim.new(0, 8),
					alignitems = Enum.ItemLineAlignment.Center,
					row_template,

					pebble.typography {
						text = "Rows:"
					},

					pebble.textfield {
						size = UDim2.fromOffset(40, 26),
						placeholder = "Rows",

						text = function()
							return rows_input()
						end,

						oninput = rows_input,

						enter = function(text)
							local n = tonumber(text)

							if n == nil then
								rows_input(tostring(rows()))
							else
								rows(n)
							end
						end
					},
				},

				pebble.row {
					spacing = UDim.new(0, 4),
					row_template,

					pebble.button {
						size = UDim2.fromOffset(26, 26),
						text = "",

						accent = function()
							return not props.paused()
						end,

						activated = function()
							props.paused(not props.paused())
						end,

						{LayoutOrder = 10},

						create "ImageLabel" {
							Size = UDim2.fromOffset(24, 24),
							Position = UDim2.fromScale(0.5, 0.5),
							AnchorPoint = Vector2.new(0.5, 0.5),

							BackgroundTransparency = 1,

							ImageColor3 = pebble.theme.fg_on_bg_high[3],

							Image = function()
								return if props.paused() then "rbxassetid://10735024209"
							else "rbxassetid://10734923214"
							end
						},

					},

					show(props.paused, function()
						return pebble.button {
							size = UDim2.fromOffset(26, 26),
							text = "",

							activated = function()
								props.refresh(true)
							end,

							create "ImageLabel" {
								Size = UDim2.fromOffset(24, 24),
								Position = UDim2.fromScale(0.5, 0.5),
								AnchorPoint = Vector2.new(0.5, 0.5),

								BackgroundTransparency = 1,

								ImageColor3 = pebble.theme.fg_on_bg_high[3],

								Image = "rbxassetid://10734933222"
							},

						}
					end)
				}

			},

			pebble.background {
				size = UDim2.fromScale(1, 0),
				automaticsize = Enum.AutomaticSize.Y,

				create "UICorner" {
					CornerRadius = UDim.new(0, 8)
				},

				create "UIFlexItem" {
					FlexMode = Enum.UIFlexMode.Fill
				},

				pebble.tablesheet {
					size = UDim2.fromScale(1, 1),
					suggested_column_sizes = { 0.1 },

					column_sizes = function()
						local t = {}
						for i in props.columns() do
							t[i] = 200
						end
						t[1] = 50
						return t
					end,
					columns = props.columns,

					read_value = function(c, r)
						local column = props.columns()[c]
						if not column then return "" end
						return column[r] or ""
					end,

					on_click = function(c, r)
						if not props.columns()[1][r-1] then return end
						spawn_app.spawn_app(entity, {
							host = props.host,
							vm = props.vm,
							id = props.id,
							entity = props.columns()[1][r-1]
						})
					end,
					on_click2 = function() end,

					below = {

						pebble.padding {
							x = UDim.new(0, 4),
							y = UDim.new(0, 2)
						},

						create "UIListLayout" {
							FillDirection = Enum.FillDirection.Horizontal,
							VerticalAlignment = Enum.VerticalAlignment.Center,
							Padding = UDim.new(0, 8)
						},

						pebble.button {
							size = UDim2.fromOffset(70, 26),
							text = "Previous",

							activated = function()
								page(page() - 1)
							end,

							disabled = function()
								return page() == 1 or props.ok() == false
							end
						} :: Instance,

						pebble.button {
							size = UDim2.fromOffset(70, 26),
							text = "Next",

							activated = function()
								page(page() + 1)
							end,

							disabled = function()
								local max_pages = math.max(1, math.ceil(props.total_entities() / rows()))
								return page() == max_pages or props.ok() == false
							end
						} :: Instance,

						pebble.typography {
							position = UDim2.new(0, 4, 0.5, 0),
							anchorpoint = Vector2.new(0, 0.5),

							text = function()
								return `total entities: {props.total_entities()}\tfrom: {props.from()}\tuntil: {props.upto()}`
							end,
						} :: Instance,
					}
				}

			}

		}

	}

end


local RunService = game:GetService("RunService")

local vide = require(script.Parent.Parent.Parent.Parent.vide)
local loop = require(script.Parent.Parent.Parent.modules.loop)
local remotes = require(script.Parent.Parent.Parent.modules.remotes)
local types = require(script.Parent.Parent.Parent.modules.types)
local widget = require(script.widget)

local cleanup = vide.cleanup

local system = {
	class_name = "app" :: "app",
	name = "System"
}

type props = {
	host: number,
	vm: number,
	scheduler: number,
	system: number,

	name: string

}

function system.mount(props: props, destroy: () -> ())

	local watch_id = math.random(2^31 - 1)
	local recording = vide.source(false)
	local watching_frame = vide.source(0)
	local per_frame_data = vide.source({} :: {[number]: number})
	local changes = vide.source({
		component = {},
		entities = {},
		types = {},
		values = {}
	} :: types.WatchLoggedChanges)

	local system_props_data = {
		watch_id = watch_id,
		host = props.host,
		vm = props.vm,
		scheduler = props.scheduler,
		system = props.system,
		name = props.name,

		changes = changes,
		recording = recording,
		per_frame_data = per_frame_data,
		watching_frame = watching_frame,
		destroy = destroy,
	}

	local app_loop = loop (
		"app-client-system",
		system_props_data,

		{i = 1},
		script.systems.replicate
	)

	local outgoing: types.OutgoingConnector = {
		host = system_props_data.host,
		to_vm = system_props_data.vm
	}

	remotes.create_watch:fire(outgoing, props.scheduler, props.system, watch_id)
	remotes.connect_watch:fire(outgoing, watch_id)

	cleanup(RunService.Heartbeat:Connect(app_loop))
	cleanup(function()
		remotes.disconnect_watch:fire(outgoing, watch_id)
		remotes.stop_watch:fire(outgoing, watch_id)
		remotes.remove_watch:fire(outgoing, watch_id)
	end)

	return widget(system_props_data)

end

return system


local vide = require(script.Parent.Parent.Parent.Parent.Parent.Parent.vide)
local queue = require(script.Parent.Parent.Parent.Parent.Parent.modules.queue)
local remotes = require(script.Parent.Parent.Parent.Parent.Parent.modules.remotes)
local types = require(script.Parent.Parent.Parent.Parent.Parent.modules.types)

local batch = vide.batch

type Context = {
	host: "server" | Player,
	vm: number,

	watch_id: number,
	scheduler: number,
	system: number,
	name: string,

	recording: vide.Source<boolean>,
	watching_frame: vide.Source<number>,
	per_frame_data: vide.Source<{[number]: number}>,
	changes: (types.WatchLoggedChanges) -> (),
}

return function(context: Context)

	local watch_id = context.watch_id
	local outgoing: types.OutgoingConnector = {
		host = context.host,
		to_vm = context.vm
	}

	local recording_state_changed = false
	local recording = false

	vide.effect(function()
		recording_state_changed = true
		recording = context.recording()
	end)

	local watching_frame_changed = false
	local watching_frame = 1
	vide.effect(function()
		watching_frame_changed = true
		watching_frame = context.watching_frame()
	end)

	local receive_update_data = queue(remotes.update_watch_data)
	local receive_overview = queue(remotes.update_overview)

	return function()

		if recording_state_changed and recording then
			remotes.start_record_watch:fire(outgoing, watch_id)
			recording_state_changed = false
		elseif recording_state_changed and not recording then
			remotes.stop_watch:fire(outgoing, watch_id)
			recording_state_changed = false
		end

		if watching_frame_changed then
			remotes.request_watch_data:fire(outgoing, watch_id, watching_frame)
			watching_frame_changed = false
		end

		debug.profilebegin("receive update data")
		batch(function()
			for from, watch, frame, changes in receive_update_data:iter() do
				if watch ~= watch_id then continue end
				if frame ~= watching_frame then continue end
				if changes == nil then
					context.changes({
						types = {},
						entities = {},
						component = {},
						values = {},
						worlds = {}
					})
				else
					context.changes(changes)
				end
			end
		end)
		debug.profileend()

		debug.profilebegin("receive overview")
		batch(function()
			for from, watch, frame, value in receive_overview:iter() do
				if watch ~= watch_id then continue end
				local data = context.per_frame_data()
				if data[frame] == value then continue end
				data[frame] = value
				context.per_frame_data(data)
			end
		end)
		debug.profileend()

	end
end


local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
local types = require(script.Parent.Parent.Parent.Parent.modules.types)
local tooltip = require(script.Parent.Parent.Parent.components.tooltip)
local virtualscroller_horizontal = require(script.Parent.Parent.Parent.components.virtualscroller_horizontal)

local create = vide.create
local source = vide.source

type props = {
	host: "server" | Player,
	vm: number,

	scheduler: number,
	system: number,
	name: string,

	recording: vide.Source<boolean>,
	watching_frame: vide.Source<number>,
	per_frame_data: () -> {[number]: number},
	changes: () -> types.WatchLoggedChanges,
}

local PROFILER_THICKNESS = 6

return function(props: props)

	local is_recording = props.recording
	local watching_frame = props.watching_frame
	local per_frame_data = props.per_frame_data
	local changes = props.changes

	local function sheet_changes()
		local changes = changes()

		return {
			{"type", unpack(changes.types)},
			{"entity", unpack(changes.entities)},
			{"component", unpack(changes.component)},
			{"value", unpack(changes.values)}
		}
	end

	local function max()
		return math.max(1, unpack(per_frame_data()))
	end

	local function total_changes()
		local sum = 0
		for _, value in per_frame_data() do
			sum += value
		end
		return sum
	end

	local hovering_over = source(false)

	return pebble.list {
		justifycontent = Enum.UIFlexAlignment.SpaceEvenly,
		spacing = UDim.new(0, 4),

		pebble.pane {
			virtualscroller_horizontal {
				item_size = PROFILER_THICKNESS,
				item = function(index)
					local function value()
						return per_frame_data()[index()] or 0
					end

					return create "TextButton" {
						Size = function()
							return UDim2.new(0, PROFILER_THICKNESS, 1, 0)
						end,
						BackgroundTransparency = function()
							return if hovering_over() == index() then 0.5 else 1
						end,
						BackgroundColor3 = pebble.theme.bg[10],

						MouseEnter = function()
							hovering_over(index())
						end,
						MouseLeave = function()
							if hovering_over() ~= index() then return end
							hovering_over(false)
						end,
						Activated = function()
							watching_frame(index())
						end,
						AutoLocalize = false,

						create "Frame" {
							Size = function()
								return UDim2.fromScale(1, value() / max())
							end,
							Position = UDim2.fromScale(1, 1),
							AnchorPoint = Vector2.new(1, 1),
							BackgroundColor3 = function()
								return if watching_frame() == index() then
									pebble.theme.acc[20]()
								elseif hovering_over() == index() then
									pebble.theme.acc[5]()
								else pebble.theme.acc[0]()
							end,
						}

					}
				end,
				max_items = function()
					return #per_frame_data()
				end,

				create "UIStroke" {Color = pebble.theme.bg[-3]},

				{
					Size = UDim2.new(1, 0, 0, 56),
					HorizontalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
					BackgroundColor3 = pebble.theme.bg[-1],
					ScrollBarThickness = 6,

					CanvasPosition = function()
						per_frame_data()
						return Vector2.new(table.maxn(per_frame_data()) * PROFILER_THICKNESS)
					end,
				}
			},

			pebble.typography {
				text = function()
					return `Recorded {#per_frame_data()} frames and tracked {total_changes()} changes`
				end
			},

			pebble.typography {
				text = function()
					return `Currently viewing frame {watching_frame()}`
				end
			}
		},

		tooltip {
			transparency = 0,
			visible = function()
				return hovering_over() ~= false
			end,

			pebble.typography {
				automaticsize = Enum.AutomaticSize.XY,

				text = function()
					return `\z
					Frame: #{hovering_over()}\n\z
					Changes: {per_frame_data()[hovering_over()] or 0}`
				end,
				xalignment = Enum.TextXAlignment.Left,
				wrapped = true
			}
		},

		pebble.container {
			Size = UDim2.fromScale(1, 0),
			AutomaticSize = Enum.AutomaticSize.Y,
			BackgroundColor3 = pebble.theme.bg[1],
			BackgroundTransparency = 0,

			create "UIListLayout" {
				FillDirection = Enum.FillDirection.Horizontal,
				HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly,
				VerticalFlex = Enum.UIFlexAlignment.Fill,
				Padding = UDim.new(0, 8)
			},

			pebble.padding {
				x = UDim.new(0, 4),
				y = UDim.new(0, 4)
			},

			pebble.button {
				size = UDim2.new(0, 80, 0, 30),
				automaticsize = Enum.AutomaticSize.X,
				text = function()
					return if is_recording() then "Pause" else "Record"
				end,

				activated = function()
					is_recording(not is_recording())
				end
			},

			pebble.container {
				Size = UDim2.fromScale(0, 0),

				create "UIFlexItem" {
					FlexMode = Enum.UIFlexMode.Fill
				},

				pebble.textfield {
					size = UDim2.new(1, 0, 1, 0),
					text = tostring(watching_frame()),
					placeholder = "frame",

					enter = function(text: string)
						if tonumber(text) == nil then return end
						watching_frame(tonumber(text))
					end
				}
			},

		},

		pebble.container {
			Size = UDim2.fromScale(1, 1),

			create "UIFlexItem" {
				FlexMode = Enum.UIFlexMode.Fill
			},

			pebble.tablesheet {
				size = UDim2.fromScale(1, 1),

				column_sizes = source {100, 80, 100, 200},
				read_value = function(column, row)
					local v = sheet_changes()[column][row]
					return if v == false then "" else v
				end,

				on_click = function() end,
				on_click2 = function() end,

				columns = sheet_changes
			}

		}
	}

end


local pebble = require(script.Parent.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.Parent.vide)
local types = require(script.Parent.Parent.Parent.Parent.modules.types)
local watch_tracker = require(script.Parent.watch_tracker)

type props = {
	host: "server" | Player,
	vm: number,

	scheduler: number,
	system: number,
	name: string,

	destroy: () -> (),

	recording: vide.Source<boolean>,
	watching_frame: vide.Source<number>,
	per_frame_data: () -> {[number]: number},
	changes: () -> types.WatchLoggedChanges,
}

return function(props: props)

	return pebble.widget {
		title = `system - {props.name}`,
		subtitle = `host: {props.host} vm: {props.vm} scheduler: {props.scheduler} system: {props.system}`,
		bind_to_close = props.destroy,
		size = Vector2.new(350, 400),
		min_size = Vector2.new(300, 300),

		watch_tracker(props)
	}

end


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

local pebble = require(script.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.vide)

local create = vide.create
local source = vide.source
local cleanup = vide.cleanup

type props = {
	visible: boolean | () -> boolean,
	transparency: number? | () -> number,
	[number]: any
}

return function(props: props)

	local mouse_location = source(Vector2.zero)

	cleanup(RunService.PreRender:Connect(function()
		mouse_location(UserInputService:GetMouseLocation())
	end))

	return create "ScreenGui" {
		Name = "Mouse Hover",
		IgnoreGuiInset = true,
		DisplayOrder = 1e9,
		Enabled = props.visible,

		create "Frame" {
			Position = function()
				return UDim2.fromOffset(
					mouse_location().X + 24,
					mouse_location().Y + 24
				)
			end,
			Size = UDim2.fromOffset(400, 0),
			AutomaticSize = Enum.AutomaticSize.XY,
			BackgroundColor3 = pebble.theme.bg[0],
			BackgroundTransparency = props.transparency or 0.5,

			pebble.padding {},

			create "UICorner" {
				CornerRadius = UDim.new(0, 8)
			},

			create "UIStroke" {
				Color = pebble.theme.bg[-10],
				Thickness = 2,
				Transparency = 0.8
			},

			unpack(props)
		}

	}

end


local pebble = require(script.Parent.Parent.Parent.Parent.pebble)
local vide = require(script.Parent.Parent.Parent.Parent.vide)

local create = vide.create
local source = vide.source
local values = vide.values
local changed = vide.changed
local effect = vide.effect
local untrack = vide.untrack

type can<T> = T | () -> T
type props = {

	size: can<UDim2>?,
	position: can<UDim2>?,
	anchorpoint: can<UDim2>?,

	--- streams in items. when index is -1, should expect to be unused
	item: (index: () -> number) -> Instance,
	--- streams in separators. when index is -1, should expect to be unused
	separator: ((index: () -> number) -> Instance)?,

	item_size: number,
	separator_size: number?,

	max_items: (() -> number)?,

	[number]: any

}

return function(props: props)

	local items = source({} :: {vide.Source<number>})

	local absolute_size = source(Vector2.zero)
	local canvas_position = source(Vector2.zero)

	local item_size = props.item_size
	local separator_size = props.separator_size or 0

	local item = props.item
	local separator = props.separator

	local OVERFLOW_ONE_SIDE = 4

	effect(function()
		local absolute_size = absolute_size()
		local canvas_position = canvas_position()

		local child_size = item_size + separator_size
		local total_required = math.ceil(absolute_size.X / child_size) + OVERFLOW_ONE_SIDE * 2
		local sources = untrack(items)

		local min_index = math.floor(canvas_position.X / child_size)
		local max_index = math.ceil((canvas_position.X + absolute_size.X) / child_size)

		local max_items = math.huge
		if props.max_items then
			max_items = props.max_items()
		end

		untrack(function()
			-- mark any sources out of range as unused
			local unused = {}

			for i, s in sources do
				local index = s()

				if
					index >= math.max(min_index, 1)
					and index <= math.min(max_index, max_items)
				then continue end
				unused[i] = true
				s(-1)
			end

			-- add sources necessary
			if #sources < total_required then
				for i = #sources + 1, total_required do
					sources[i] = source(-1)
					unused[i] = true
				end
				items(sources)
			end

			-- update indexes of any sources that went unused
			local did_not_render = {}

			for i = math.max(min_index, 1), math.min(max_index, max_items) do
				did_not_render[i] = true
			end

			for _, s in sources do
				did_not_render[s()] = nil
			end

			for index in unused do
				local s = sources[index]
				local key = next(did_not_render)
				if not key then break end
				s(key)
				did_not_render[key] = nil
				unused[index] = nil
			end

			if next(did_not_render) then warn("missing source!", next(did_not_render)) end

			-- remove unnecessary sources
			if #sources > total_required then
				for i = #sources, 1, -1 do
					if unused[i] then
						table.remove(sources, i)
					end
					unused[i] = nil
					if #sources < total_required then break end
				end
				items(sources)
			end

		end)

	end)

	return create "ScrollingFrame" {

		Size = props.size or UDim2.fromScale(1, 1),
		Position = props.position,
		AnchorPoint = props.anchorpoint,

		BackgroundTransparency = 1,

		CanvasSize = function()
			if props.max_items then
				return UDim2.fromOffset(props.max_items() * (item_size + separator_size), 0)
			else
				local absolute_size = absolute_size()
				local canvas_position = canvas_position()
				local child_size = item_size + separator_size
				local max_index = math.ceil((canvas_position.X + absolute_size.X) / child_size) + OVERFLOW_ONE_SIDE
				return UDim2.fromOffset(max_index * child_size, 0)
			end
		end,

		values(items, function(index)
			return create "Frame" {
				Name = index,

				Position = function()
					if index() == -1 then UDim2.fromOffset(0, -1000) end
					return UDim2.fromOffset(
						(item_size + separator_size) * (index() - 1),
						0
					)
				end,
				Size = UDim2.new(0, item_size + separator_size, 1, 0),

				BackgroundTransparency = 1,

				pebble.container {
					Name = "Item",

					item(index),
				},

				if separator then
					pebble.container {
						Name = "Separator",

						separator(index)
					}
				else nil,

			}
		end),

		changed("AbsoluteSize", absolute_size),
		changed("CanvasPosition", canvas_position),

		unpack(props),

	}

end


local entity = require(script.apps.entity)
local home = require(script.apps.home)
local overview_scheduler = require(script.apps.overview_scheduler)
local registry = require(script.apps.registry)
local spawn_app = require(script.spawn_app)

return {

	apps = {
		home = home,
		entity = entity,
		scheduler = overview_scheduler,
		registry = registry,
	},

	spawn_app = spawn_app.spawn_app,
	unmount_all = spawn_app.unmount_all

}


local Players = game:GetService("Players")

local vide = require(script.Parent.Parent.Parent.vide)
local types = require(script.Parent.Parent.modules.types)

local destroy_fn = {}

local function unmount_all()
	for destroy in destroy_fn do
		destroy()
	end
end

local function spawn_app<T>(app: types.Application<T>, props: T): () -> ()
	return vide.root(function(destroy)

		local destroy = function()
			destroy_fn[destroy] = nil
			destroy()
		end

		local application = app.mount(props, destroy)
		application.Parent = Players.LocalPlayer.PlayerGui

		vide.cleanup(application)

		destroy_fn[destroy] = true

		return destroy
	end)
end

return {
	unmount_all = unmount_all,
	spawn_app = spawn_app
}


local jecs = require(script.Parent.jecs)
local traffic_check = require(script.modules.traffic_check)
local types = require(script.modules.types)
local vm_id = require(script.modules.vm_id)
local server = require(script.server)
local public = require(script.server.public)
local scheduler = require(script.server.scheduler)

type Applet<T> = {
	--- this is currently a temporary measure, while i work on a more
	--- long-term solution for jabby 0.3.
	add_to_public: (name: string, config: T) -> ()
}

local world_applet = {
	add_to_public = function(
		name: string, config: { world: jecs.World, entities: {[Instance]: jecs.Entity<any>}?, get_entity_from_part: ((part: BasePart) -> (jecs.Entity<any>, Part?))? }
	)
		public.updated = true
		table.insert(public, {
			class_name = "World",
			name = name,
			world = config.world,
			entities = config.entities,
			get_entity_from_part = config.get_entity_from_part
		})
	end
}

local scheduler_applet = {
	add_to_public = function(
		name: string, config: { scheduler: types.Scheduler }
	)
		public.updated = true
		config.scheduler.name = name
		table.insert(public, config.scheduler)
	end
}

return {
	set_check_function = function(callback: (Player) -> boolean)
		traffic_check.can_use_jabby = callback
	end,

	obtain_client = function()
		return require(script.client)
	end,

	vm_id = vm_id,
	scheduler = scheduler,

	broadcast_server = server.broadcast,

	applets = {
		world = world_applet,
		scheduler = scheduler_applet
	},

	register = function<T>(info: { name: string, applet: Applet<T>, configuration: T })
		info.applet.add_to_public(info.name, info.configuration)
	end,

}


local function average(n: {number})
	local sum = 0
	for i, v in n do
		sum += v
	end
	return sum / #n
end

return average


local function convert_units(unit: string, value: number): (string)
	local s = math.sign(value)
	value = math.abs(value)
    local prefixes = {
        [4] = "T",
        [3] ="G",
        [2] ="M",
        [1] = "k",
        [0] = " ",
        [-1] = "m",
        [-2] = "u",
        [-3] = "n",
        [-4] = "p"
    }

    local order = 0

    while value >= 1000 do
        order += 1
        value /= 1000
    end

    while value ~= 0 and value < 1 do
        order -= 1
        value *= 1000
    end

    if value >= 100 then
        value = math.floor(value)
    elseif value >= 10 then
        value = math.floor(value * 1e1) / 1e1
    elseif value >= 1 then
        value = math.floor(value * 1e2) / 1e2
    end

    return value * s .. prefixes[order] .. unit
end

return convert_units


local types = require(script.Parent.types)

return function(connector: types.IncomingConnector | types.OutgoingConnector)
	return `{connector.host}\0{connector.from_vm or connector.to_vm}`
end


local scheduler = require(script.Parent.Parent.server.scheduler)
type Array<T> = { T }

export type System = (any, number) -> ...(any, number) -> ()

type GroupInfo = { i: number?, o: number? }

type SystemGroup = {
    interval: number,
    offset: number,
    dt: number,
    [number]: {
        id: number,
        name: string,
        type: number,
        fn: (...any) -> ...any
    }
}

local function loop_create(name: string, data: any, ...: ModuleScript | () -> () | GroupInfo)
    local jabby_scheduler = scheduler.create(name)

    local groups = {} :: Array<SystemGroup>

    local current_group: SystemGroup?

    local function process_systems(array: Array<any>)
        for i, v in array do
            if type(v) == "table" then
                if v.i then
                    if current_group then
                        table.insert(groups, current_group)
                    end

                    current_group = {
                        interval = v.i or 1,
                        offset = v.o or 0,
                        dt = 0
                    }
                else
                    process_systems(v)
                end
            elseif type(v) == "function" then
                assert(current_group)

                table.insert(current_group, {
                    id = jabby_scheduler:register_system(),
                    name = "UNNAMED",
                    type = 0,
                    fn = v
                })
            else
                assert(current_group)

                local fn = (require :: any)(v) :: System
                local fn2 = fn(data, 0)

                table.insert(current_group, {
                    id = jabby_scheduler:register_system({name = `{v.Name}`}),
                    name = v.Name,
                    type = fn2 and 1 or 0,
                    fn = fn2 or fn
                })
            end
        end
    end

    process_systems { ... }

    assert(current_group)
    table.insert(groups, current_group)
    current_group = nil

    local frame_count = 0

    return function(dt)
        frame_count += 1

        debug.profilebegin("ECS LOOP")

        for _, group in groups do
            group.dt += dt

            if frame_count % group.interval == group.offset then
                for _, system in ipairs(group) do
                    debug.setmemorycategory(system.name)
                    debug.profilebegin(system.name)

                    if system.type == 0 then
                        jabby_scheduler:run(system.id, system.fn, data, group.dt)
                    else
                        jabby_scheduler:run(system.id, system.fn, group.dt)
                    end

                    debug.profileend()
                end

                group.dt = 0
            end
        end

        debug.resetmemorycategory()
        debug.profileend()
    end, jabby_scheduler
end

return loop_create


--[[

	net is a utility library designed to handle connections to other actors and
	the server for me.

]]

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

local signal = require(script.Parent.signal)
local vm_id = require(script.Parent.vm_id)
local traffic_check = require(script.Parent.traffic_check)
local types = require(script.Parent.types)

local local_host: "server" | Player
local MANAGER_VM = 0

if RunService:IsServer() then
	local_host = "server"
else
	local_host = Players.LocalPlayer
end

local function tincoming_connector(t: any): boolean
	if typeof(t) ~= "table" then return false end
	if not (t.host == "server" or (typeof(t.host) == "Instance" and t.host:IsA("Player"))) then return false end
	if typeof(t.from_vm) ~= "number" then return false end
	if t.to_vm ~= nil and typeof(t.to_vm) ~= "number" then return false end

	return true
end

local NAME = "JABBY_REMOTES"
local folder: Instance
if RunService:IsServer() then
	local existing = ReplicatedStorage:FindFirstChild(NAME)
	if existing then
		folder = existing
	else
		folder = Instance.new("Folder")
		folder.Name = NAME
		folder.Archivable = false
		folder.Parent = ReplicatedStorage
	end
else
	folder = ReplicatedStorage:WaitForChild(NAME)
end

local function get_remote_event(name: string, unreliable: boolean?): RemoteEvent & { actor: BindableEvent, peer: RemoteEvent }
	if RunService:IsServer() then
		return folder:FindFirstChild(name) :: RemoteEvent & { actor: BindableEvent }
			or (function()
				local remote = Instance.new(if unreliable then "UnreliableRemoteEvent" else "RemoteEvent")
				remote.Name = name
				remote.Parent = folder

				local fire_actor = Instance.new("BindableEvent")
				fire_actor.Name = "actor"
				fire_actor.Parent = remote

				local peer = Instance.new("RemoteEvent")
				peer.Name = "peer"
				peer.Parent = remote

				return remote :: RemoteEvent & { actor: BindableEvent, peer: RemoteEvent }
			end)()
	else
		return folder:WaitForChild(name) :: RemoteEvent & { actor: BindableEvent }
	end
end

local function create_event<T...>(name: string, unreliable: boolean?, do_not_block_traffic: boolean?)
	local remote = get_remote_event(name, unreliable)
	local on_event_fire, fire = signal()

	local event = {
		type = "event",

		fire = function(_, connector: types.OutgoingConnector, ...)
			--- if the host is within this vm, we can fire it straight to
			if not traffic_check.check(local_host, connector.host, true) then return end

			-- same host, same vm.
			if
				connector.host == local_host
				and connector.to_vm == vm_id
			then

				local incoming = {
					host = local_host,
					from_vm = vm_id,
					to_vm = connector.to_vm
				}

				fire(incoming, ...)
			--- if the host is the same, but in a separate actor
			--- we have to fire the actor
			elseif
				connector.host == local_host
				and connector.to_vm ~= vm_id
			then
				local incoming = {
					host = local_host,
					from_vm = vm_id,
					to_vm = connector.to_vm
				}

				remote.actor:Fire(incoming, ...)
			--- we need to fire the server
			elseif connector.host == "server" then
				local incoming = {
					host = "server",
					from_vm = vm_id,
					to_vm = connector.to_vm
				}

				remote:FireServer(incoming, ...)
			--- we need to fire the client
			elseif local_host == "server" then
				local incoming = {
					host = "server",
					from_vm = vm_id,
					to_vm = connector.to_vm
				}

				remote:FireClient(connector.host, incoming, ...)
			--- we need to tell the server to redirect this to the client
			else
				local incoming = {
					host = connector.host,
					from_vm = vm_id,
					to_vm = connector.to_vm
				}

				remote:FireServer(incoming, ...)
			end

		end,

		connect = function(_, callback: (types.IncomingConnector, T...) -> ())
			on_event_fire:connect(callback :: any)
		end
	}

	if RunService:IsServer() then
		remote.OnServerEvent:Connect(function(player, target: types.IncomingConnector, ...)
			--- check if the player is allowed to send this
			if not do_not_block_traffic and not traffic_check.check(player, target.host) then
				return
			end

			--- check if its a proper connector
			if not tincoming_connector(target) then return end

			if target.host == "server" and (target.to_vm == vm_id or target.to_vm == nil) then
				target.host = player
				fire(target, ...)
			elseif target.host ~= "server" and vm_id == MANAGER_VM then
				local to = target.host
				target.host = player
				remote:FireClient(
					to,
					target,
					...
				)
			end
		end)
	else
		remote.OnClientEvent:Connect(function(incoming: types.IncomingConnector, ...)
			-- print("receive", remote.Name, "from", incoming.host)
			if tincoming_connector(incoming) == false then return end
			if incoming.to_vm ~= vm_id and incoming.to_vm ~= nil then return end
			traffic_check._whitelist(local_host, incoming.host)

			fire(incoming, ...)
		end)
	end

	remote:WaitForChild("actor").Event:Connect(function(incoming: types.IncomingConnector, ...)
		if incoming.to_vm ~= vm_id and incoming.to_vm ~= nil then return end
		fire(incoming, ...)
	end)

	return (event :: any) :: types.NetEvent<T...>
end

return {
	create_event = create_event,
	local_host = local_host
}


--- Licensed under MIT from centau_ri
export type Queue<T...> = typeof(setmetatable(
	{} :: {
		add: (self: Queue<T...>, T...) -> (),
		clear: (self: Queue<T...>) -> (),
		iter: (self: Queue<T...>) -> () -> T...,
	},
	{} :: {
		__len: (self: Queue<T...>) -> number,
		__iter: (self: Queue<T...>) -> () -> T...,
	}
))

type Array<T> = { T }

local Queue = {}
do
	Queue.__index = Queue

	type _Queue = Queue<...any> & {
		size: number,
		columns: Array<Array<unknown>>,
	}

	function Queue.new<T...>(): Queue<T...>
		local self: _Queue = setmetatable({
			size = 0,
			columns = {},
		}, Queue) :: any

		setmetatable(self.columns, {
			__index = function(columns: Array<Array<unknown>>, idx: number)
				columns[idx] = {}
				return columns[idx]
			end,
		})

		return self :: Queue<T...>
	end

	function Queue.add(self: _Queue, ...: unknown)
		-- iteration will stop if first value is `nil`
		assert((...) ~= nil, "first argument cannot be nil")

		local columns = self.columns
		local n = self.size + 1
		self.size = n

		for i = 1, select("#", ...) do
			columns[i][n] = select(i, ...)
		end
	end

	function Queue.clear(self: _Queue)
		self.size = 0
		for _, column in next, self.columns do
			table.clear(column)
		end
	end

	local function iter(self: _Queue)
		local columns = self.columns
		local n = self.size
		local i = 0

		if #columns <= 1 then
			local column = columns[1]
			return function()
				i += 1
				local value = column[i]
				if i == n then self:clear() end
				return value
			end
		else
			local tuple = table.create(#columns)
			return function()
				i += 1
				for ci, column in next, columns do
					tuple[ci] = column[i]
				end
				if i == n then self:clear() end
				return unpack(tuple)
			end
		end
	end

	Queue.iter = iter
	Queue.__iter = iter

	function Queue.__len(self: _Queue)
		return self.size
	end
end

type ISignal<T...> = {
	connect: (self: any, listener: (T...) -> ()) -> (),
} | {
	Connect: (self: any, listener: (T...) -> ()) -> (),
}

local queue_create = function<T...>(signal: ISignal<T...>?): Queue<T...>
	local queue = Queue.new()

	if signal then
		local connector = (signal :: any).connect or (signal :: any).Connect
		assert(connector, "signal has no member `connect()`")
		connector(signal, function(...)
			queue:add(...)
		end)
	end

	return queue
end :: (<T...>() -> Queue<T...>) & (<T...>(signal: ISignal<T...>) -> Queue<T...>)

return queue_create


--!nocheck
local types = require(script.Parent.types)

local function reverse(connector: types.IncomingConnector): types.OutgoingConnector
	return {
		host = connector.host,
		to_vm = connector.from_vm,
	}
end

return reverse


--[[

	A rudimentary signal class. Yielding may cause bugs.

]]

local signal = {}
signal.__index = signal

type Connection = { disconnect: (any?) -> (), reconnect: (any?) -> () }
export type Signal<T... = ...unknown> = {

	class_name: "Signal",

	connect: (Signal<T...>, callback: (T...) -> ()) -> Connection,
	wait: (Signal<T...>) -> T...,
	once: (Signal<T...>, callback: (T...) -> ()) -> Connection,

	callbacks: { [(T...) -> ()]: true },
}
export type SignalInternal<T... = ...unknown> = Signal<T...> & {
	fire: (SignalInternal<T...>, T...) -> (),
}

function signal.connect<T...>(self: Signal<T...>, callback: (T...) -> ())
	assert(type(callback) == "function")
	self.callbacks[callback] = true

	return {
		disconnect = function() self.callbacks[callback] = nil end,
		reconnect = function() self.callbacks[callback] = true end,
	}
end

function signal.fire<T...>(self: Signal<T...>, ...: T...)
	for callback in self.callbacks do
		callback(...)
	end
end

function signal.once<T...>(self: Signal<T...>, callback: (T...) -> ())
	local connection
	connection = self:connect(function(...)
		connection:disconnect()
		callback(...)
	end)

	return connection
end

function signal.wait<T...>(self: Signal<T...>)
	local thread = coroutine.running()

	local connection = self:connect(function(...) coroutine.resume(thread, ...) end)
	local packed = { coroutine.yield() }
	connection:disconnect()
	return unpack(packed)
end

local function new_signal<T...>(): (Signal<T...>, (T...) -> ())
	local self = setmetatable({
		class_name = "Signal",
		callbacks = {},
	}, signal)

	local function fire(...)
		for callback in self.callbacks :: any do
			callback(...)
		end
	end

	return self :: any, fire
end

return new_signal


local Players = game:GetService("Players")
--[[

a utility library to handle checking traffic and determining if the sender is
permitted to send the given data.

]]

local signal = require(script.Parent.signal)

local traffic_check = {}

local whitelist_player_to = {}

local on_fail, fire = signal()

--- A function that needs to be overwritten by the user.
--- This function is used to find out what permissions a user may have.
traffic_check.can_use_jabby = function(player: Player)
	local is_studio = game:GetService("RunService"):IsStudio()

	return is_studio --is_owner or is_studio
end

--- Runs a callback defined by the developer to determine if a player is allowed
--- to use a given function
local function communication_is_allowed(from: "server" | Player, to: "server" | Player, dont_whitelist: boolean?)
	if from == "server" then return true end

	whitelist_player_to[from] = whitelist_player_to[from] or {}
	whitelist_player_to[to] = whitelist_player_to[to] or {}

	if traffic_check.can_use_jabby(from) or whitelist_player_to[from][to] then
		if dont_whitelist then return true end
		whitelist_player_to[to][from] = from
		return true
	else
		return false
	end
end

--- Runs the given check and fires the on_fail signal if the player fails the
--- check.
local function check(from: "server" | Player, to: "server" | Player, dont_whitelist: boolean?)
	if communication_is_allowed(from, to, dont_whitelist) then
		return true
	else
		fire(from)
		return false
	end
end

local function check_no_wl(from: "server" | Player)
	if from == "server" then return true end
	if traffic_check.can_use_jabby(from) then
		return true
	else
		-- print(from, "cant use jabby")
		fire(from)
		return false
	end
end

local function _whitelist(from: "server" | Player, to: "server" | Player)
	whitelist_player_to[from] = whitelist_player_to[from] or {}
	whitelist_player_to[to] = whitelist_player_to[to] or {}
	whitelist_player_to[from][to] = from
end

traffic_check.communication_is_allowed = communication_is_allowed
traffic_check.check_no_wl = check_no_wl
traffic_check.check = check

traffic_check._whitelist = _whitelist

traffic_check.on_fail = on_fail

Players.PlayerRemoving:Connect(function(player)
	whitelist_player_to[player] = nil
end)

return traffic_check


local jecs = require(script.Parent.Parent.Parent.jecs)
type host = "client" | "server"

export type IncomingConnector = {
	host: Player | "server",
	from_vm: number,
	to_vm: number
}

export type OutgoingConnector = {
	host: Player | "server",
	to_vm: number?, -- not specifying a vm makes it received by all
	from_vm: nil
}

export type NetEvent<T...> = {

	type: "event",

	fire: (any, connector: OutgoingConnector, T...) -> (),
	connect: (any, callback: (connector: IncomingConnector, T...) -> ()) -> RBXScriptConnection,
}

export type NetCallback<T..., U...> = {

	type: "callback",

	invoke: (any, connector: OutgoingConnector, T...) -> U...,
	set_callback: (any, callback: (connector: IncomingConnector, T...) -> U...) -> (),
}

export type SystemId = number

export type SystemTag = "processing" | "finished" | "paused"
export type SystemSettingData = {
	name: string?,
	phase: string?,
	layout_order: number?,
	paused: boolean?
}
export type SystemData = {
	name: string,
	phase: string?,
	layout_order: number,
	paused: boolean
}

type ChangeTypes = "remove" | "clear" | "delete" | "add" | "set" | "entity" | "component"
export type WatchLoggedChanges = {
	types: {ChangeTypes},
	entities: {jecs.Entity<any>},
	component: {jecs.Entity<any>},
	values: {string},
	worlds: {jecs.World}
}

export type SystemWatch = {
	--- enables Lua Object Notation.
	--- incurs a significant performance penalty.
	enable_lon: boolean,
	--- the current frame to process
	frame: number,

	frames: {[number]: WatchLoggedChanges}
}

export type SystemLabel = {}

export type SystemFrame = {
	i: number,
	s: number
}

type WatchData = {active: boolean, watch: SystemWatch, untrack: () -> ()}

export type Scheduler = {
	class_name: "Scheduler",
	name: string,

	valid_system_ids: {[SystemId]: true},
	system_data: {[SystemId]: SystemData},
	system_data_updated: {[SystemId]: true},
	system_frames: {[SystemId]: SystemFrame},
	system_frames_updated: {[SystemId]: {[SystemFrame]: true}},
	system_watches: {[SystemId]: {WatchData}},

	register_system: (Scheduler, settings: SystemSettingData?) -> SystemId,
	set_system_data: (Scheduler, system: SystemId, settings: SystemSettingData) -> (),
	get_system_data: (Scheduler, system: SystemId) -> SystemSettingData,
	create_watch_for_system: (Scheduler, system: SystemId) -> WatchData,
	remove_system: (Scheduler, system: SystemId) -> (),

	-- mark_system_frame_start: (Scheduler, system: SystemId) -> (),
	-- mark_system_frame_end: (Scheduler, system: SystemId, s: number?) -> (),
	-- append_extra_frame_data: (Scheduler, system: SystemId, label: SystemLabel) -> (),
	--- this should call mark_system_frame_start and mark_system_frame_end for you
	run: <T...>(Scheduler, system: SystemId, system: () -> (), T...) -> (),

}

export type World = {
	class_name: "World",
	name: string,
	world: jecs.World,

	entities: {[Instance]: jecs.Entity<any>}?,
	get_entity_from_part: ((part: BasePart) -> (jecs.Entity<any>?, Part?))?
}

export type Application<T> = {
	class_name: "app",
	name: string,

	mount: (props: T, destroy: () -> ()) -> Instance
}

return nil


--------------------------------------------------------------------------------
-- videx/store.luau
--------------------------------------------------------------------------------
local vide = require(script.Parent.Parent.Parent.vide)
local source = vide.source

local NULL = newproxy()

local Store = {}

--[=[
Creates a new store object that receives some initial state and then returns
a table with the same structure, but all keys of the given table will be reactive.

When accessed inside a reactive scope, the reactive scope will update whenever
the key that is accessed is changed.

@param initial_state `T : {[string]: any}` The initial state the store will start in.
@param mutations `() -> {[string]: (T, ...any) -> ...any}?` A list of functions that mutate the data.
@return `T & U` A resulting table that
]=]
function Store.new<T, U>(
    initial_state: T & {},
    mutations: (T & U) -> U
): T & U
    local sources = {}

    for i, v in initial_state :: any do
        local src = source(v ~= NULL and v or nil)
        sources[i] = src
    end

    local internal_proxy = {}

    setmetatable(internal_proxy, {
        __index = function(_, index)
            return sources[index]()
        end,
        __newindex = function(_, index, value)
            sources[index](value)
        end
    })

    local external_proxy = {}

    setmetatable(external_proxy :: any, {
        __index = function(_, index)
            local src = sources[index]
            if src == nil then error(`invalid index {index}`, 2) end
            return src()
        end,

        __newindex = function(_, index, value)
            sources[index](value)
        end
    })

    for i, v in next, mutations(internal_proxy :: any) :: any do
        if rawget(external_proxy, i) then
            error(`duplicate field "{i}"`, 2)
        end
        rawset(external_proxy, i, v)
    end

    return external_proxy :: T & U & {}
end

--- A special symbol used to indicate that a value should be nil within a Store.
Store.null = NULL :: nil

return Store


--[[

Provides a unique identifier for a VM.

This currently cannot be tested unless there is some parallel system for jest.

]]

local SharedTableRegistry = game:GetService("SharedTableRegistry")

local shared_table = SharedTableRegistry:GetSharedTable("_gorp_common_vm_count")
shared_table.id = shared_table.id or 0

return SharedTable.increment(shared_table, "id", 1)


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

local loop = require(script.Parent.modules.loop)
local remotes = require(script.Parent.modules.remotes)
local traffic_check = require(script.Parent.modules.traffic_check)
local vm_id = require(script.Parent.modules.vm_id)
local function broadcast()
	for _, player in Players:GetPlayers() do
		if not traffic_check.can_use_jabby(player) then continue end
		remotes.new_server_registered:fire({
			host = player,
		})
	end
end

task.delay(0, broadcast)

local systems = script.systems
local loop = loop (
	`jabby-host:{
		if RunService:IsServer() then "server" else "client"
	}-vm:{vm_id}`,
	nil,
	{i = 1},

	systems.ping,
	systems.replicate_core,
	systems.replicate_scheduler,
	systems.replicate_registry,
	systems.replicate_system_watch,
	systems.mouse_pointer,
	systems.entity
)

RunService.PostSimulation:Connect(loop)

return {

	broadcast = broadcast

}


--[[

anything in here is considered "public" and will be visible to jabby clients

]]

local self = {

	updated = false,

} :: {
	updated: boolean,
	[number]: any
}

return self