--!strict

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

type ExplorerFrame = Types.ExplorerFrame
type Manager = Types.Manager

local RULER_POOL_SIZE = 20
local rulerPool: {GuiObject} = {}

local RulerPool = {}

function RulerPool.fillToLimit(Manager: Manager)
	if #rulerPool >= RULER_POOL_SIZE then return end
	local framesToCreate = RULER_POOL_SIZE - #rulerPool
	for _ = 1, framesToCreate do
		local ruler = Instance.new("Frame")
		ruler.Name = "StickyRuler"
		ruler.BorderSizePixel = 0

		table.insert(rulerPool, ruler)
	end
end

function RulerPool.getRuler(Manager: Manager): Frame
	if #rulerPool == 0 then
		RulerPool.fillToLimit(Manager)
	end
	return table.remove(rulerPool) :: Frame
end

function RulerPool.releaseRuler(Manager: Manager, frame: ExplorerFrame)
	local state = Manager.WidgetState
	local frameWorld = state.FrameWorld
	local components = Manager.Components

	local ruler = frameWorld:get(frame, components.FRAMEWORLD_Ruler)
	if not ruler then return end

	--ruler.Visible = false
	ruler.Size = UDim2.fromOffset(0, 0)
	ruler.Parent = nil
	frameWorld:remove(frame, components.FRAMEWORLD_Ruler)
	table.insert(rulerPool, ruler)
end

function RulerPool.assignRuler(Manager: Manager, frame: ExplorerFrame, frameX: number, stickyY: number, rulerHeight: number, indentWidth: number)
	local state = Manager.WidgetState
	local frameWorld = state.FrameWorld
	local components = Manager.Components

	local ruler = frameWorld:get(frame, components.FRAMEWORLD_Ruler)
	if not ruler then
		local _ruler = RulerPool.getRuler(Manager)
		_ruler.Parent = state.InstanceTree
		frameWorld:set(frame, components.FRAMEWORLD_Ruler, _ruler)
		ruler = _ruler
	end
	local ruler = assert(ruler)

	ruler.Position = UDim2.fromOffset(frameX + indentWidth, stickyY + Manager.getSetting("FrameHeight"))
	ruler.Size = UDim2.fromOffset(1, math.max(0, rulerHeight))
	--ruler.Visible = rulerHeight > 0
end

return RulerPool


local Types = require("../../Types")
local UDF = require("../UDF")
local default_style = require("../Defaults/DefaultStyle")
local default_settings = require("../Defaults/DefaultSettings")
local default_invisible_classes = require("../Defaults/DefaultInvisibleClasses")
local Constants = require("../../Constants")

local function noop() end

local function reset_styles(self, state, manager)
	local defaultTokens, defaultRules = default_style(manager)
	local rules: {Types.SerializedStyleRule} = {}
	local tokens = {}

	self._serialize_rules(defaultRules, rules, nil, "Default")
	for k, v in defaultTokens do
		tokens[k] = {
			value = UDF.to(v),
			tags = {Constants.DefaultTokenTag}
		}
	end
	state.style.rules = rules
	state.style.tokens = tokens
end

local function reset_invisible_classes(self, state, manager)
	local dict = {}
	for _, v in default_invisible_classes do
		dict[v] = true
	end
	state.invisible = dict
end

local function reset_settings(self, state, manager, name: string)
	local default = table.clone(state.settings)
	local configs = default_settings
	for name, v in configs[name] do
		default[name] = UDF.to(v.Default)
	end
	state.settings = default
end

local migrations
migrations = {
	v6_to_v7 = noop,
	v7_to_v8 = noop,
	v8_to_v9 = noop,
	v9_to_v10 = noop,
	v10_to_v11 = noop,
	v11_to_v12 = noop,
	v12_to_v13 = reset_invisible_classes,
	v13_to_v14 = noop,
	v14_to_v15 = noop,
	v15_to_v16 = noop,
	v16_to_v17 = reset_styles,
	v17_to_v18 = function(self, state, manager)
		reset_settings(self, state, manager, "ExplorerFrame")
	end,
	v18_to_v19 = function(self, state, manager)
		reset_settings(self, state, manager, "Internal")
		reset_styles(self, state, manager)
	end,
	v19_to_v20 = function(self, state, manager)
		state.icon_colors = {}
	end,
	v20_to_v21 = function(self, state, manager)
		state.icon_colors = {}
		state.versions = {}
		state.tabs = {}
		state.repo_proxies = {}
	end,
	v21_to_v22 = function(self, state, manager)
		reset_settings(self, state, manager, "Scrolling")
		reset_styles(self, state, manager)
	end,
	v22_to_v23 = function(self, state, manager)
		for _, v in state.tabs do
			for _, tab in v do
				tab.layout_order = 1
			end
		end
	end,
	v23_to_v24 = reset_styles,
	v24_to_v25 = noop,
	v25_to_v26 = function(self, state, manager)
		reset_settings(self, state, manager, "Keybinds")
	end,
	v26_to_v27 = reset_styles,
	v27_to_v28 = reset_styles
}
:: {[string]: (any, Types.SavedState, Types.Manager) -> Types.SavedState?}

return migrations


--!strict
local plugin = script:FindFirstAncestorOfClass("Plugin")

local Style = require("./Style")
local Types = require("../Types")

local DefaultOrder = require("./Defaults/DefaultOrder")
local DefaultInvisibleClasses = require("./Defaults/DefaultInvisibleClasses")
local DefaultSettings = require("./Defaults/DefaultSettings")
local DefaultStyle = require("./Defaults/DefaultStyle")

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

local UDF = require("./UDF")
local Migrations = require("@self/Migrations")

export type SavedState = Types.SavedState

local SavedState = {
	data_version = 28
}

function SavedState._migrate(state: any, manager: Types.Manager): SavedState
	local current_version = state.data_version or 1
	if current_version == SavedState.data_version then
		return state
	end

	if current_version > SavedState.data_version then
		warn(string.format(
			"SavedState version %d is newer than supported version %d. Using defaults.",
			current_version,
			SavedState.data_version
			))
		return SavedState.get_default(manager)
	end

	print(string.format("Migrating SavedState from v%d to v%d", current_version, SavedState.data_version))

	for version = current_version, SavedState.data_version - 1 do
		local migration_fn = Migrations[string.format("v%d_to_v%d", version, version + 1)]
		if migration_fn then
			local success, result = pcall(migration_fn, SavedState, state, manager)
			if success then
				state = result or state
				print(string.format("  ✓ Migrated to v%d", version + 1))
			else
				warn(string.format("Migration v%d_to_v%d failed: %s", version, version + 1, tostring(result)))
				warn("Falling back to defaults")
				return SavedState.get_default(manager)
			end
		else
			warn(string.format("Missing migration function: v%d_to_v%d", version, version + 1))
		end
	end

	state.data_version = SavedState.data_version
	print(string.format("Migration complete: now at v%d", state.data_version))
	return state
end

function SavedState._serialize_rules(source: {Style.Rule}, destination: any, excludedTag: string?, includedTag: string?)
	for _, rule in source do
		if excludedTag and rule:HasTag(excludedTag) then continue end
		if includedTag and not rule:HasTag(includedTag) then continue end
		local subrules = {}
		if #rule.ChildRules > 0 then
			SavedState._serialize_rules(rule.ChildRules, subrules)
		end
		local tokens = {}
		for name, token in rule.Tokens do
			local tags = {}
			for tag in token.Tags do table.insert(tags, tag) end
			tokens[name] = {
				value = UDF.to(token.RawValue),
				tags = tags
			}
		end
		local attributes = {}
		for name, value in rule.Attributes do
			attributes[name] = UDF.to(value)
		end
		table.insert(destination, {
			attributes = attributes,
			tokens = tokens,
			display_name = rule:GetName(),
			selector = rule.Rule.Selector,
			priority = rule.Rule.Priority,
			props = (function()
				local props = {}
				for k, v in rule.RawProperties do
					props[k] = UDF.to(v)
				end
				return props
			end)(),
			tags = (function()
				local tags = {}
				for tag in rule.Tags do
					table.insert(tags, tag)
				end
				return tags
			end)(),
			rules = subrules
		})
	end
end

function SavedState.get_default(manager: Types.Manager): SavedState
	local rules, tokens = {}, {}
	local configs = {}
	local invis = {}

	local dtokens, drules = DefaultStyle(manager)
	for k, v in dtokens do
		tokens[k] = {
			value = UDF.to(v),
			tags = {Constants.DefaultTokenTag}
		}
	end

	SavedState._serialize_rules(drules, rules)
	for _, t in DefaultSettings do
		for k, v in t do
			configs[k] = UDF.to(v.Default)
		end
	end
	for _, v in DefaultInvisibleClasses do
		invis[v] = true
	end

	return {
		icon_colors = {},
		repo_proxies = {},
		tabs = {},
		display_order = {
			default_order = 999,
			class_order = DefaultOrder(manager)
		},
		invisible = invis,
		data_version = SavedState.data_version,
		settings = configs,
		style = {
			tokens = tokens,
			rules = rules
		},
		versions = {}
	}
end

function SavedState.save(state: SavedState)
	state.data_version = SavedState.data_version
	plugin:SetSetting("Jexplorer_SavedState", state)
end

function SavedState.load(manager: Types.Manager): SavedState
	local saved = plugin:GetSetting("Jexplorer_SavedState")

	if not saved then
		print("No saved state found, creating defaults")
		saved = SavedState.get_default(manager)
		return saved
	end

	if not saved.data_version or saved.data_version ~= SavedState.data_version then
		saved = SavedState._migrate(saved, manager)
		saved.data_version = SavedState.data_version
	end

	return saved
end

return SavedState


return function<T>(tbl: T, name: string): T
	return setmetatable(tbl, {
		__tostring = function()
			return name
		end,
	})
end


--!strict
local Jecs = require(script.Parent.Parent.Parent.Jecs)
local Types = require(script.Parent.Parent.Parent.Types)

type World = Types.World
type ExplorerEntity = Types.ExplorerEntity

local childrenOf = (function(world: World, parent: ExplorerEntity, a, b, c)
	if a and b and c then
		return world:query(a, b, c, Jecs.pair(Jecs.ChildOf, parent))::any
	elseif a and b then
		return world:query(a, b, Jecs.pair(Jecs.ChildOf, parent))::any
	elseif a then
		return world:query(a, Jecs.pair(Jecs.ChildOf, parent))::any
	else
		return world:query(Jecs.pair(Jecs.ChildOf, parent))::any
	end
end::any)::
((World, ExplorerEntity) -> Jecs.Query<>)
&	(<A>(World, ExplorerEntity, Jecs.Id<A>) -> Jecs.Query<A>)
&	(<A, B>(World, ExplorerEntity, Jecs.Id<A>, Jecs.Id<B>) -> Jecs.Query<A, B>)
&	(<A, B, C>(World, ExplorerEntity, Jecs.Id<A>, Jecs.Id<B>, Jecs.Id<C>) -> Jecs.Query<A, B, C>)

return childrenOf


--!strict
local StudioService = game:GetService('StudioService')
local getclassicon = StudioService.GetClassIcon

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

local ClassIcon = {
	CachedIcons = {}
}

function ClassIcon.getIcon(className: string): Types.StudioIconResult?
	local cached = ClassIcon.CachedIcons[className]
	if cached then return cached end

	local icon = getclassicon(StudioService, className)
	ClassIcon.CachedIcons[className] = icon

	return icon
end

function ClassIcon.loadIcons(classes: ReflectedClasses)
	for _, v in classes do
		ClassIcon.getIcon(v.Name)
	end
end

return ClassIcon


return function(frameSize: Vector2, originalPosition: Vector2, size: Vector2, containerPos: Vector2, containerSize: Vector2): (number, number)
	local left = containerPos.X
	local top = containerPos.Y
	local right = containerPos.X + containerSize.X
	local bottom = containerPos.Y + containerSize.Y

	local desiredX = originalPosition.X
	local desiredY = originalPosition.Y + size.Y
	if desiredX + frameSize.X > right then
		desiredX = right - frameSize.X
	end
	if desiredX < left then
		desiredX = left
	end
	if desiredY + frameSize.Y > bottom then
		desiredY = originalPosition.Y - frameSize.Y
		if desiredY < top then
			desiredY = bottom - frameSize.Y
		end
	end
	if desiredY < top then
		desiredY = top
	end
	return 	math.max(left, math.min(desiredX, right - frameSize.X)),
			math.max(top, math.min(desiredY, bottom - frameSize.Y))
end


local ReflectionService = game:GetService("ReflectionService")

local propMapCache: {[string]: ReflectedProperties} = {}

return function(className: string): {[string]: ReflectedProperty}
	local cached = propMapCache[className]
	if cached then return cached end

	local success, props = pcall(function()
		return ReflectionService:GetPropertiesOfClass(className, {
			Security = SecurityCapabilities.fromCurrent()
		})
	end)
	if not success or not props then
		local propMap = {
			["ClassName"] = {},
			["Name"] = {}
		}
		propMapCache[className] = propMap
		return propMap
	end
	local propMap = {}
	for _, info in props do
		propMap[info.Name] = info
	end
	propMapCache[className] = propMap
	return propMap
end


local ChangeHistoryService = game:GetService("ChangeHistoryService")

return {
	beginRecording = function(name: string)
		local id = ChangeHistoryService:TryBeginRecording(name, name)
		if not id then
			ChangeHistoryService:SetWaypoint(name)
		end
		return id
	end,
	finishRecording = function(id: string?, operation: Enum.FinishRecordingOperation?)
		if id then
			ChangeHistoryService:FinishRecording(id, operation or Enum.FinishRecordingOperation.Commit)
		end
	end,
}


local formats = {
	TaggedProperty = "JEXP_TAGGED_%s_%s"
}

local JexpTags = {
	Formats = formats
}

function JexpTags.getTaggedPropertyTag(propName: string, targetedValue: string)
	return formats.TaggedProperty:format(propName, targetedValue)
end

return JexpTags


local _, newindex: (Instance, string, ...any) -> () = xpcall(function()
	game[""] = nil
end, function()
	return debug.info(2, "f")
end)
local _, index: (Instance, string) -> ...any = xpcall(function()
	local b = game[""]
end, function()
	return debug.info(2, "f")
end)

return table.freeze({
	index = function(self, k)
		if typeof(self) == "Instance" then
			return index(self, k)
		end
		return self[k]
	end,
	newindex = function(self, k, v)
		if typeof(self) == "Instance" then
			newindex(self, k, v)
			return
		end
		self[k] = v
	end,

	rawindex = index,
	rawnewindex = newindex,

	rbxconnect = game.Destroying.Connect,
	getpropertychangedsignal = game.GetPropertyChangedSignal
})


local ChangeHistoryService = game:GetService("ChangeHistoryService")

type fn = () -> ()

local lastRecordingId: string?
local lastRecordingState: Enum.FinishRecordingOperation?
local function onEnd(id, operationObject, ...)
	if id and lastRecordingId == id then
		ChangeHistoryService:FinishRecording(id, operationObject.v)
	end
	lastRecordingState = operationObject.v
	return ...
end

return function<T...>(name: string, fn: (cancel: fn, append: fn, commit: fn) -> T...): T...
	local id = ChangeHistoryService:TryBeginRecording(name, name)
	if not id then
		if lastRecordingId then
			ChangeHistoryService:FinishRecording(lastRecordingId, lastRecordingState or Enum.FinishRecordingOperation.Cancel)
			id = ChangeHistoryService:TryBeginRecording(name, name)
			if not id then
				-- Final fallback
				ChangeHistoryService:SetWaypoint(name)
			else
				lastRecordingId = id
			end
		else
			ChangeHistoryService:SetWaypoint(name)
		end
	else
		lastRecordingId = id
	end
	local operationObject = {v = Enum.FinishRecordingOperation.Commit}

	return onEnd(id, operationObject, fn(function()
		operationObject.v = Enum.FinishRecordingOperation.Cancel
	end, function()
		operationObject.v = Enum.FinishRecordingOperation.Append
	end, function()
		operationObject.v = Enum.FinishRecordingOperation.Commit
	end))
end


local StudioService = game:GetService('StudioService')
local Constants = require("../../Constants")

local JexpTags = require("./JexpTags")
local format = string.format

local function getTaggedPropertySelector(propName: string, targetedValue: string)
	return `.{JexpTags.getTaggedPropertyTag(propName, targetedValue)}`
end

local function getIconSelector(name: string?, class: string?)
	if not name and not class then
		return "#ClassIcon"
	elseif not name and class then
		return `.JEXP_ExplorerFrame #{Constants.ExplorerFrameNameFormat:format(class)} > #Main > #ClassIcon`
	elseif name and not class then
		return `{getTaggedPropertySelector("Name", name)} > #Main > #ClassIcon`
	elseif name and class then
		return `{getTaggedPropertySelector("ClassName", class)} {getTaggedPropertySelector("Name", name)} > #Main > #ClassIcon`
	end
end

local function getClassTagSlctr(tag: string, ...: string)
	local selector = {}
	for _, class in {...} do
		table.insert(selector, `{tag} {class}`)
	end
	return table.concat(selector, ",")
end

local function parseSelectorMacro(selector: string): (string?, string)
	selector = selector:gsub("%s", "")
	if selector:sub(1, 1) == "@" then
		print(selector)

		local parens = selector:match("%b()")
		assert(parens, "macro must contain parens")
		selector = selector:sub(2, #selector - #parens)
		parens = parens:sub(2, #parens - 1)

		local values = string.split(parens, ",")
		if selector == "icon" then
			return getIconSelector(
				values[1] ~= "nil" and values[1] or nil,
				values[2] ~= "nil" and values[2] or nil
			), "icon"
		elseif selector == "prop" then
			return getTaggedPropertySelector(
				values[1],
				values[2]
			), "prop"
		elseif selector == "tag" then
			return `.JEXP_TAG_{values[1]}`, "tag"
		end
	end
	return nil, "none"
end

return {
	getClassTagSlctr = getClassTagSlctr,
	parseSelectorMacro = parseSelectorMacro,
	getTaggedPropertySelector = getTaggedPropertySelector,
	getIconSelector = getIconSelector
}


return function(containerWidth: number, frameX: number, offset: number)
	local padding = 20

	return UDim2.new(
		0,
		containerWidth - frameX - padding + offset,
		0.5,
		0
	)
end


local Types = require("../../Types")
local RawInst = require("./RawInst")

local GetStyled = game.GetStyled
local rawindex = RawInst.rawindex

local textlabel = Instance.new("TextLabel")
textlabel.Visible = false
textlabel.Name = "BoundsCalcProxy"

return {
	SetProxyParent = function(parent: Instance)
		textlabel.Parent = parent
	end,

	GetBounds = function(Manager: Types.Manager, from: TextButton | TextLabel | TextBox): Vector2
		textlabel.Text = rawindex(from, "Text")

		textlabel.TextSize = GetStyled(from, "TextSize")
		textlabel.FontFace = GetStyled(from, "FontFace")

		return rawindex(textlabel, "TextBounds")
	end
}


local xyz = require("./xyz")

local ToStringUtils = {
	Color3 = {},
}

local COL3_FMT = "%.2f, %.2f, %.2f"
local FLOAT_FMT = "%.2f"
local VEC2_MT = "%.2f, %.2f"

local XY_PATT = "%s*([%d.-]*)%s*,%s*([%d.-]*)%s*"

function ToStringUtils.fromVec2String(str: string)
	local x, y = str:match(XY_PATT)
	return Vector2.new(
		assert(tonumber(x)),
		assert(tonumber(y))
	)
end

function ToStringUtils.toVec2String(vec2: Vector2)
	return VEC2_MT:format(vec2.X, vec2.Y)
end

function ToStringUtils.fromRGBString(str: string)
	local r, g, b = xyz(str)
	return Color3.fromRGB(
		assert(tonumber(r)),
		assert(tonumber(g)),
		assert(tonumber(b))
	)
end

function ToStringUtils.toRGBString(col: Color3)
	return COL3_FMT:format(col.R * 255, col.G * 255, col.B * 255)
end

function ToStringUtils.toFloatString(num: number)
	return FLOAT_FMT:format(num)
end

function ToStringUtils.fromFloatString(str: string)
	return assert(tonumber(str))
end

return ToStringUtils


return function(str: string): (any, any, any)
	return (string.match :: any)(str, "%s*([%d.-]*)%s*,%s*([%d.-]*)%s*,%s*([%d.-]*)%s*")
end


--!strict
--!optimize 2

local eval = require(script.EvalASEL)
local SimpleSignal = require(script.Parent.Vendor.SimpleSignal)

export type Expression = {
	__stylevar: boolean,
	__expr: string
}

export type Cloned = {
	__clonedrule: boolean,
	__name: string
}

export type StyleToken = {
	__styletoken: boolean,
	Name: string?,
	RawValue: any,
	AppliedValue: any,
	Sheet: Style?,
	Tags: {[string]: boolean},

	AddTag: (StyleToken, string) -> (),
	HasTag: (StyleToken, string) -> boolean,
	RemoveTag: (StyleToken, string) -> (),

	init: (StyleToken, sheet: Style, name: string) -> (),
	evaluate: (StyleToken) -> (),
	__initialized: boolean,
}

export type Style = {
	Sheet: StyleSheet,
	Tokens: {[string]: StyleToken},
	AppliedTokens: {[string]: any},
	TokenDependencies: {[string]: {string}},

	Links: {StyleLink},
	Rules: {Rule},
	Link: (Style, gui: Instance) -> StyleLink,

	SetToken: (Style, name: string, value: (StyleToken | any)?, autoUpdate: boolean) -> (),
	GetToken: (Style, name: string) -> any,
	GetRawToken: (Style, name: string) -> StyleToken?,
	UpdateTokens: (Style) -> (),
	UpdateToken: (Style, name: string) -> (),
	GetDependencies: (Style, expr: string) -> {string},

	AddRules: (Style, rules: {[any]: any}) -> (),
	FindRule: (Style, selector: string) -> Rule?,
	GetTagged: (Style, tag: string) -> {Rule},
	GetWithoutTags: (Style, ...string) -> {Rule},
	Replace: (Style, newStyle: Style) -> (),

	SlctrToRule: {[string]: Rule},

	RuleAdded: SimpleSignal.SimpleSignal<Rule>,
	RuleRemoved: SimpleSignal.SimpleSignal<Rule>,
	TokenChanged: SimpleSignal.SimpleSignal<string, StyleToken>,
	AppliedTokenChanged: SimpleSignal.SimpleSignal<string, any>,
}

export type Rule = {
	Rule: StyleRule,
	Sheet: Style,
	ChildRules: {Rule},
	ParentRule: Rule?,
	GetTokens: (Rule) -> {[string]: any},
	Tags: {[string]: boolean},
	PropertyDependencies: {[string]: {string}},
	Attributes: {[string]: any},

	GetChild: (Rule, childName: string) -> Rule,

	SetAttribute: (Rule, name: string, value: any) -> (),
	GetAttribute: (Rule, name: string) -> any?,

	Tokens: {[string]: StyleToken},
	AppliedTokens: {[string]: any},
	TokenDependencies: {[string]: {string}},
	TokenChanged: SimpleSignal.SimpleSignal<string, StyleToken>,
	AppliedTokenChanged: SimpleSignal.SimpleSignal<string, any>,
	SetToken: (Rule, name: string, value: (StyleToken | any)?, autoUpdate: boolean) -> (),
	GetToken: (Rule, name: string) -> any,
	GetRawToken: (Rule, name: string) -> StyleToken?,
	UpdateTokens: (Rule) -> (),
	UpdateToken: (Rule, name: string) -> (),

	AddTag: (Rule, string) -> (),
	HasTag: (Rule, string) -> boolean,
	RemoveTag: (Rule, string) -> (),
	GetTagged: (Rule, tag: string) -> {Rule},
	GetWithoutTags: (Rule, ...string) -> {Rule},

	RawProperties: {[string]: any},
	AppliedProperties: {[string]: any},

	AddRules: (Rule, rules: {Rule}) -> (),
	SetProperty: (Rule, k: string, value: any) -> (),
	GetProperty: (Rule, k: string) -> any,
	GetName: (Rule) -> string,
	SetName: (Rule, name: string) -> (),
	Destroy: (Rule) -> (),

	PropertyChanged: SimpleSignal.SimpleSignal<string, any>,
	AppliedPropertyChanged: SimpleSignal.SimpleSignal<string, any>,
	RuleAdded: SimpleSignal.SimpleSignal<Rule>,
	RuleRemoved: SimpleSignal.SimpleSignal<Rule>,

	init: (Rule, sheet: Style) -> (),
	__initialized: boolean
}

local RULE_NAME_FORMAT = "%s"

local Style = {
	eval = eval
}

local function unwrapValue(v: any): any
	if type(v) == "table" and v.__styletoken then
		return (v :: StyleToken).RawValue
	end
	return v
end

local function getDependencies(expr: string): {string}
	local deps = {}
	local seen = {}
	for token in expr:gmatch("%$([%w_]+)") do
		if not seen[token] then
			seen[token] = true
			table.insert(deps, token)
		end
	end
	return deps
end

local function topologicalSort(tokens: {[string]: any}, tokenDeps: {[string]: {string}}): {string}
	local sorted = {}
	local visited = {}
	local visiting = {}

	local function visit(name: string)
		if visited[name] then return end
		if visiting[name] then
			error(`Circular dependency detected involving token '${name}'`)
		end
		visiting[name] = true
		local deps = tokenDeps[name]
		if deps then
			for _, dep in deps do
				if tokens[dep] ~= nil then
					visit(dep)
				end
			end
		end
		visiting[name] = nil
		visited[name] = true
		table.insert(sorted, name)
	end

	for name in tokens do
		if type(name) == "string" and not visited[name] then
			visit(name)
		end
	end

	return sorted
end

local function buildRuleTokenContext(rule: Rule): {[string]: any}
	local chain: {Rule} = {}
	local cursor: Rule? = rule
	while cursor do
		table.insert(chain, 1, cursor)
		cursor = cursor.ParentRule
	end
	local resolved: {[string]: any} = {}
	if rule.Sheet then
		for k, v in rule.Sheet.AppliedTokens do
			resolved[k] = v
		end
	end
	for _, r in chain do
		for k, v in r.AppliedTokens do
			resolved[k] = v
		end
	end
	return resolved
end

local function evalProperty(inner: any, ctx: {[string]: any}): any
	if type(inner) == "table" and inner.__stylevar then
		return eval(inner.__expr, ctx)
	elseif type(inner) == "string" and inner:sub(1, 1) == "$" then
		return ctx[inner:sub(2)]
	else
		return inner
	end
end

local function updateRulesRecursive(rules: {Rule})
	for _, ruleTbl in rules do
		local ctx = buildRuleTokenContext(ruleTbl)
		for k, v in ruleTbl.RawProperties do
			local inner = unwrapValue(v)
			local evaluated = evalProperty(inner, ctx)
			ruleTbl.Rule:SetProperty(k, evaluated)
			ruleTbl.AppliedProperties[k] = evaluated
			ruleTbl.AppliedPropertyChanged:Fire(k, evaluated)
		end
		if #ruleTbl.ChildRules > 0 then
			updateRulesRecursive(ruleTbl.ChildRules)
		end
	end
end

-- StyleToken metatable -------------------------------------------------------

local TokenMethods = {} :: StyleToken;
(TokenMethods :: any).__index = TokenMethods;
(TokenMethods :: any).__tostring = function(self: StyleToken)
	return `StyleToken({tostring(self.RawValue)})`
end

function TokenMethods:AddTag(tag: string)
	self.Tags[tag] = true
end

function TokenMethods:HasTag(tag: string): boolean
	return self.Tags[tag] == true
end

function TokenMethods:RemoveTag(tag: string)
	self.Tags[tag] = nil
end

function TokenMethods:evaluate()
	local sheet = self.Sheet
	assert(sheet, "StyleToken must be initialized before calling evaluate()")
	local name = self.Name :: string
	local raw = self.RawValue

	local applied: any
	if type(raw) == "table" and raw.__stylevar then
		applied = eval(raw.__expr, sheet.AppliedTokens)
	else
		applied = raw
	end

	self.AppliedValue = applied
	sheet.AppliedTokens[name] = applied
	pcall(sheet.Sheet.SetAttribute, sheet.Sheet, name, applied)
end

function TokenMethods:init(sheet: Style, name: string)
	assert(not self.__initialized, `StyleToken '${name}' has already been initialized.`)
	self.__initialized = true
	self.Sheet = sheet
	self.Name = name

	local raw = self.RawValue
	if type(raw) == "table" and raw.__stylevar then
		sheet.TokenDependencies[name] = getDependencies(raw.__expr)
	else
		sheet.TokenDependencies[name] = {}
	end

	self:evaluate()
end

------------------------------------------------------------------------------

local function filterRules(style: Style, rules: {[string | number]: any}, tokens: {[string]: StyleToken})
	for k, v in rules do
		if type(k) == "number" then continue end
		local tok: StyleToken
		if type(v) == "table" and v.__styletoken then
			tok = v :: StyleToken
		else
			tok = Style.token(v)
		end
		tokens[k] = tok
		rules[k] = nil
	end

	for name, tok in tokens do
		local raw = tok.RawValue
		if type(raw) == "table" and raw.__stylevar then
			style.TokenDependencies[name] = getDependencies(raw.__expr)
		else
			style.TokenDependencies[name] = {}
		end
	end

	local sortedTokens = topologicalSort(tokens, style.TokenDependencies)
	for _, tokenName in sortedTokens do
		local tok = tokens[tokenName]
		if not tok.__initialized then
			tok:init(style, tokenName)
		end
	end

	for _, rule: Rule in rules do
		if rule.__initialized then continue end
		rule:init(style)
	end
end

local StyleMethods = {} :: Style
(StyleMethods :: any).__index = StyleMethods

function StyleMethods:Link(gui)
	local sheet = self.Sheet
	local link = Instance.new("StyleLink")
	link.StyleSheet = sheet
	link.Parent = gui
	table.insert(self.Links, link)
	return link
end

function StyleMethods:GetToken(name: string): any
	return self.AppliedTokens[name]
end

function StyleMethods:GetRawToken(name: string): StyleToken?
	return self.Tokens[name]
end

function StyleMethods:GetDependencies(expr: string): {string}
	return getDependencies(expr)
end

local function buildReverseDeps(tokenDeps: {[string]: {string}}): {[string]: {string}}
	local reverseDeps: {[string]: {string}} = {}
	for tokenName, deps in tokenDeps do
		for _, depToken in deps do
			if not reverseDeps[depToken] then
				reverseDeps[depToken] = {}
			end
			table.insert(reverseDeps[depToken], tokenName)
		end
	end
	return reverseDeps
end

function StyleMethods:UpdateToken(name: string)
	local reverseDeps = buildReverseDeps(self.TokenDependencies)

	local toUpdate = {[name] = true}
	local queue = {name}
	local queueIdx = 1
	while queueIdx <= #queue do
		local current = queue[queueIdx]
		queueIdx += 1
		local dependents = reverseDeps[current]
		if dependents then
			for _, dependent in dependents do
				if not toUpdate[dependent] then
					toUpdate[dependent] = true
					table.insert(queue, dependent)
				end
			end
		end
	end

	local tokensToUpdate = {}
	for tokenName in toUpdate do
		tokensToUpdate[tokenName] = self.Tokens[tokenName]
	end
	for _, tokenName in topologicalSort(tokensToUpdate, self.TokenDependencies) do
		local tok = self.Tokens[tokenName]
		tok:evaluate()
		self.AppliedTokenChanged:Fire(tokenName, tok.AppliedValue)
	end

	updateRulesRecursive(self.Rules)
end

function StyleMethods:SetToken(name: string, value: (StyleToken | any)?, autoUpdate: boolean)
	if not value then
		local tok: StyleToken = self.Tokens[name]
		if not tok then return end

		self.Tokens[name] = nil
		self.AppliedTokens[name] = nil
		self.TokenDependencies[name] = nil

		pcall(self.Sheet.SetAttribute, self.Sheet, name, nil)

		tok.Sheet = nil
		tok.Name = nil
		tok.__initialized = false
		self.TokenChanged:Fire(name, nil)

		return
	end

	local tok: StyleToken
	if type(value) == "table" and (value :: any).__styletoken then
		tok = value
	else
		tok = Style.token(value :: any)
	end

	tok.__initialized = false
	tok:init(self, name)
	self.Tokens[name] = tok
	self.TokenChanged:Fire(name, tok)

	if autoUpdate then
		self:UpdateToken(name)
	end
end

function StyleMethods:UpdateTokens()
	for _, tokenName in topologicalSort(self.Tokens, self.TokenDependencies) do
		local tok = self.Tokens[tokenName]
		tok:evaluate()
		self.AppliedTokenChanged:Fire(tokenName, tok.AppliedValue)
	end

	updateRulesRecursive(self.Rules)
end

function StyleMethods:Replace(new: Style)
	local sheet = self.Sheet
	new.Sheet.Parent = sheet.Parent

	for _, v in self.Links do
		v.StyleSheet = new.Sheet
	end
	sheet:Destroy()

	self.Sheet = new.Sheet
	table.clear(self.Rules)
	for _, v in new.Rules do
		table.insert(self.Rules, v)
		self.RuleAdded:Fire(v)
	end
	table.clear(self.Tokens)
	for k, v in new.Tokens do
		self.Tokens[k] = v
		self.TokenChanged:Fire(k, v)
	end
	table.clear(self.AppliedTokens)
	for k, v in new.AppliedTokens do
		self.AppliedTokens[k] = v
	end
	table.clear(self.TokenDependencies)
	for k, v in new.TokenDependencies do
		self.TokenDependencies[k] = v
	end
end

function StyleMethods:AddRules(newRules)
	filterRules(self, newRules, self.Tokens)
	for _, v in newRules do
		self.Sheet:InsertStyleRule(v.Rule)
		table.insert(self.Rules, v)
		self.RuleAdded:Fire(v)
	end
end

function StyleMethods:FindRule(selector)
	return self.SlctrToRule[selector]
end

function StyleMethods:GetTagged(tag: string): {Rule}
	local tagged = {}
	for _, rule in self.Rules do
		if rule:HasTag(tag) then
			table.insert(tagged, rule)
		end
	end
	return tagged
end

function StyleMethods:GetWithoutTags(...: string): {Rule}
	local excludeTags = {...}
	local result = {}
	for _, rule in self.Rules do
		local hasExcludedTag = false
		for _, tag in excludeTags do
			if rule:HasTag(tag) then
				hasExcludedTag = true
				break
			end
		end
		if not hasExcludedTag then
			table.insert(result, rule)
		end
	end
	return result
end

-- Rule metatable -------------------------------------------------------------

local RuleMethods = {} :: Rule
(RuleMethods :: any).__index = RuleMethods

function RuleMethods:AddTag(tag)
	self.Tags[tag] = true
end

function RuleMethods:HasTag(tag)
	return self.Tags[tag] == true
end

function RuleMethods:RemoveTag(tag)
	self.Tags[tag] = nil
end

function RuleMethods:GetTagged(tag: string): {Rule}
	local tagged = {}
	for _, rule in self.ChildRules do
		if rule:HasTag(tag) then
			table.insert(tagged, rule)
		end
	end
	return tagged
end

function RuleMethods:GetWithoutTags(...: string): {Rule}
	local excludeTags = {...}
	local result = {}
	for _, rule in self.ChildRules do
		local hasExcludedTag = false
		for _, tag in excludeTags do
			if rule:HasTag(tag) then
				hasExcludedTag = true
				break
			end
		end
		if not hasExcludedTag then
			table.insert(result, rule)
		end
	end
	return result
end

function RuleMethods:GetTokens(): {[string]: any}
	return buildRuleTokenContext(self)
end

function RuleMethods:GetToken(name: string): any
	return self.AppliedTokens[name]
end

function RuleMethods:GetRawToken(name: string): StyleToken?
	return self.Tokens[name]
end

function RuleMethods:SetToken(name: string, value: (StyleToken | any)?, autoUpdate: boolean)
	if not value then
		local tok: StyleToken = self.Tokens[name]
		if not tok then return end
		self.Tokens[name] = nil
		self.AppliedTokens[name] = nil
		self.TokenDependencies[name] = nil
		tok.Sheet = nil
		tok.Name = nil
		tok.__initialized = false
		self.TokenChanged:Fire(name, nil)
		return
	end

	local tok: StyleToken
	if type(value) == "table" and (value :: any).__styletoken then
		tok = value
	else
		tok = Style.token(value :: any)
	end

	self.AppliedTokens = buildRuleTokenContext(self)
	local fakeSheet = {
		AppliedTokens = self.AppliedTokens,
		TokenDependencies = self.TokenDependencies,
		Sheet = { SetAttribute = function() end },
	}

	tok.__initialized = false
	tok:init(fakeSheet :: any, name)
	self.Tokens[name] = tok
	self.TokenChanged:Fire(name, tok)

	if autoUpdate then
		self:UpdateToken(name)
	end
end

function RuleMethods:UpdateToken(name: string)
	local reverseDeps = buildReverseDeps(self.TokenDependencies)
	local toUpdate = {[name] = true}
	local queue = {name}
	local queueIdx = 1
	while queueIdx <= #queue do
		local current = queue[queueIdx]
		queueIdx += 1
		local dependents = reverseDeps[current]
		if dependents then
			for _, dependent in dependents do
				if not toUpdate[dependent] then
					toUpdate[dependent] = true
					table.insert(queue, dependent)
				end
			end
		end
	end

	self.AppliedTokens = buildRuleTokenContext(self)
	local fakeSheet = {
		AppliedTokens = self.AppliedTokens,
		TokenDependencies = self.TokenDependencies,
		Sheet = { SetAttribute = function() end },
	}
	local tokensToUpdate = {}
	for tokenName in toUpdate do
		tokensToUpdate[tokenName] = self.Tokens[tokenName]
	end
	for _, tokenName in topologicalSort(tokensToUpdate, self.TokenDependencies) do
		local tok = self.Tokens[tokenName]
		tok.Sheet = fakeSheet :: any
		tok:evaluate()
		self.AppliedTokenChanged:Fire(tokenName, tok.AppliedValue)
	end

	updateRulesRecursive({self})
end

function RuleMethods:UpdateTokens()
	local fakeSheet = {
		AppliedTokens = self.AppliedTokens,
		TokenDependencies = self.TokenDependencies,
		Sheet = { SetAttribute = function() end },
	}
	for _, tokenName in topologicalSort(self.Tokens, self.TokenDependencies) do
		local tok = self.Tokens[tokenName]
		tok.Sheet = fakeSheet :: any
		tok:evaluate()
		self.AppliedTokenChanged:Fire(tokenName, tok.AppliedValue)
	end

	updateRulesRecursive({self})
end

function RuleMethods:SetProperty(k, v)
	local ctx = buildRuleTokenContext(self)
	local inner = unwrapValue(v)
	local evaluated = evalProperty(inner, ctx)

	if type(inner) == "table" and inner.__stylevar then
		self.PropertyDependencies[k] = getDependencies(inner.__expr)
	elseif type(inner) == "string" and inner:sub(1, 1) == "$" then
		self.PropertyDependencies[k] = {inner:sub(2)}
	else
		self.PropertyDependencies[k] = {}
	end

	self.Rule:SetProperty(k, evaluated)
	self.RawProperties[k] = v
	self.AppliedProperties[k] = evaluated
	self.PropertyChanged:Fire(k, v)
end

function RuleMethods:AddRules(rules)
	local sheet = self.Sheet
	for _, rule in rules do
		rule:init(sheet)
	end
	for _, v in rules do
		v.ParentRule = self
		table.insert(self.ChildRules, v)
		v.Rule.Parent = self.Rule
		self.RuleAdded:Fire(v)
	end
end

function RuleMethods:GetProperty(k)
	return self.Rule:GetProperty(k)
end

function RuleMethods:GetName()
	return self.Rule:GetAttribute("Name")
end

function RuleMethods:SetName(new)
	self.Rule:SetAttribute("Name", new)
end

function RuleMethods:GetChild(name)
	for _, v in self.ChildRules do
		if v:GetName() == name then return v end
	end
	error(`Couldnt find child Rule '{name}' of Rule '{self:GetName()}'`)
end

function RuleMethods:GetAttribute(name)
	return self.Attributes[name]
end

function RuleMethods:SetAttribute(name, value)
	self.Attributes[name] = value
end

function RuleMethods:Destroy()
	self.Sheet.RuleRemoved:Fire(self)
	if self.ParentRule then
		self.ParentRule.RuleRemoved:Fire(self)
		local tbl = self.ParentRule.ChildRules
		local idx = table.find(tbl, self)
		if idx then
			local n = #tbl
			tbl[idx] = tbl[n]
			tbl[n] = nil
		end
	end
	local rule = self.Rule
	local rules = self.Sheet.Rules

	local idx = table.find(rules, self)
	if idx then
		local l = #rules
		rules[idx] = rules[l]
		rules[l] = nil
	end
	rule:Destroy()
	self.Sheet.SlctrToRule[self.Rule.Selector] = nil
	table.clear(self.RawProperties)
	table.clear(self.AppliedProperties)

	self.PropertyChanged:Destroy()
	self.AppliedPropertyChanged:Destroy()
	self.RuleAdded:Destroy()
	self.RuleRemoved:Destroy()
	self.TokenChanged:Destroy()
	self.AppliedTokenChanged:Destroy()
end

function RuleMethods:init(sheet)
	assert(not self.__initialized, `rule '{self:GetName()}' has already been initialized.`)
	self.__initialized = true
	self.Sheet = sheet

	sheet.SlctrToRule[self.Rule.Selector] = self

	local rule = self.Rule

	if not self.ParentRule then
		rule.Parent = sheet.Sheet
	else
		rule.Parent = self.ParentRule.Rule
	end

	local ctx = buildRuleTokenContext(self)
	local realProps = self.AppliedProperties
	local props = self.RawProperties
	for k, v in props do
		local inner = unwrapValue(v)
		local evaluated = evalProperty(inner, ctx)
		realProps[k] = evaluated
		if type(inner) == "table" and inner.__stylevar then
			self.PropertyDependencies[k] = getDependencies(inner.__expr)
		elseif type(inner) == "string" and inner:sub(1, 1) == "$" then
			self.PropertyDependencies[k] = {inner:sub(2)}
		else
			self.PropertyDependencies[k] = {}
		end
	end
	rule:SetProperties(realProps)

	local nameMap = {}
	for _, v in sheet.Rules do
		nameMap[v:GetName()] = v
	end

	local rulesToInit: {Rule} = {}
	local clonesToResolve = {}

	for i, v: any in self.ChildRules do
		if typeof(v) == "table" and v.__clonedrule then
			local existing = nameMap[v.__name]
			assert(existing, `Rule '{v.__name}' does not exist. Cannot use Style.cloned() rule`)
			local cloned = Style.cloneRule(existing)
			cloned.ParentRule = self
			clonesToResolve[i] = cloned
			table.insert(rulesToInit, cloned)
		else
			v.ParentRule = self
			table.insert(rulesToInit, v)
		end
	end
	for i, cloned in clonesToResolve do
		self.ChildRules[i] = cloned
	end

	for _, childRule in rulesToInit do
		childRule:init(sheet)
	end
end

-- Style constructors ---------------------------------------------------------

function Style.var(expr: string): Expression
	return {
		__stylevar = true,
		__expr = expr
	}
end

function Style.token(value: any): StyleToken
	return setmetatable({
		__styletoken = true,
		__initialized = false,
		RawValue = value,
		AppliedValue = nil,
		Name = nil,
		Sheet = nil,
		Tags = {},
	} :: any, TokenMethods) :: StyleToken
end

function Style.cloned(ruleName: string): Cloned
	return {
		__clonedrule = true,
		__name = ruleName
	}
end

function Style.cloneRule(rule: Rule)
	local cloned = Style.rule(rule:GetName(), rule.Rule.Selector, rule.Rule.Priority, rule.RawProperties)
	local clonedChildren = {}
	for _, v in rule.ChildRules do
		table.insert(clonedChildren, Style.cloneRule(v))
	end
	cloned:AddRules(clonedChildren)
	return cloned
end

function Style.rule(name: string, selector: string, priority: number, props: {[any]: any}): Rule
	local rule = Instance.new("StyleRule")
	rule.Selector = selector
	if rule.SelectorError ~= "" then
		error(rule.SelectorError)
	end
	rule.Priority = priority
	rule.Name = RULE_NAME_FORMAT:format(selector)
	rule:SetAttribute("Name", name)

	local arrayPart = {}
	local childrenRules = {}
	for i, v in ipairs(props :: any) do
		table.insert(arrayPart, i)
		table.insert(childrenRules, v)
	end
	for _, i in arrayPart do
		props[i] = nil
	end

	local realProps = {}
	local ruleTbl: Rule = setmetatable({
		Rule = rule,
		RawProperties = props,
		AppliedProperties = realProps,
		ChildRules = childrenRules,
		Tags = {},
		PropertyDependencies = {},
		Attributes = {},

		Tokens = {},
		AppliedTokens = {},
		TokenDependencies = {},
		TokenChanged = SimpleSignal.new(),
		AppliedTokenChanged = SimpleSignal.new(),
		AppliedPropertyChanged = SimpleSignal.new(),
		PropertyChanged = SimpleSignal.new(),
		RuleAdded = SimpleSignal.new(),
		RuleRemoved = SimpleSignal.new()
	} :: Rule, RuleMethods) :: any

	local currentSlctr = rule.Selector
	rule:GetPropertyChangedSignal("Selector"):Connect(function()
		if ruleTbl.Sheet then
			ruleTbl.Sheet.SlctrToRule[currentSlctr] = nil
			ruleTbl.Sheet.SlctrToRule[rule.Selector] = ruleTbl
			currentSlctr = rule.Selector
		end
		rule.Name = RULE_NAME_FORMAT:format(rule.Selector)
	end)

	return ruleTbl
end

function Style.create(rules: {[any]: any}): Style
	local sheet = Instance.new("StyleSheet")

	local tokens = {}
	local style = setmetatable({
		Sheet = sheet,
		Tokens = tokens,
		Links = {},
		AppliedTokens = {},
		TokenDependencies = {},
		Rules = rules,

		RuleAdded = SimpleSignal.new(),
		RuleRemoved = SimpleSignal.new(),
		TokenChanged = SimpleSignal.new(),
		AppliedTokenChanged = SimpleSignal.new(),
		SlctrToRule = {}
	} :: Style, StyleMethods) :: any

	filterRules(style, rules, tokens)

	for _, v in rules do
		sheet:InsertStyleRule(v.Rule)
	end

	return style
end

function Style.fromRulesTokens(rules: {Rule}, tokens: {[string]: any}): Style
	local tbl = {}
	for k, v in tokens do
		tbl[k] = v
	end
	for i, v in rules do
		tbl[i :: any] = v
	end
	local sheet = Style.create(tbl)
	sheet:UpdateTokens()
	return sheet
end

return Style


local create = require("./create")

return {
	JexplorerUi = function()
		return create "Frame" {
			Name = "JexplorerUi",
			"JEXP_Background",

			create "Frame" {
				Name = "InstanceTree",
				"JEXP_DarkBackground",

				create "ScrollingFrame" {
					Name = "TreeScroller",
					"JEXP_ScrollingFrame",
				},
				create "Frame" {
					Name = "ScrollContainer"
				},
				create "Frame" {
					Name = "ScrollBackgroundHorizontal",
					ZIndex = 4,

					"JEXP_DarkBackground",
					"JEXP_GrayBorder",
					"JEXP_ScrollBackgroundHorizontal"
				},
				create "Frame" {
					Name = "ScrollBackgroundVertical",
					ZIndex = 4,

					"JEXP_DarkBackground",
					"JEXP_GrayBorder",
					"JEXP_ScrollBackgroundVertical"
				},
			},
			create "Frame" {
				Name = "Topbar",

				"JEXP_Background",
				"JEXP_Border",
				create "Frame" {
					Name = "PathBar",
					"JEXP_Background",
					"JEXP_Border",
					create "ScrollingFrame" {
						Name = "Segments",

						create "TextButton" {
							Name = "PathSegment",
							"JEXP_Text"
						}
					}
				},
				create "Frame" {
					Name = "TabBar",
					"JEXP_Background",

					create "ScrollingFrame" {
						Name = "Tabs",
					}
				},
				create "ImageButton" {
					Name = "Options",
					"JEXP_MonochromeImage"
				},
				create "Frame" {
					Name = "QueryInputFrame",
					"JEXP_Border",
					"JEXP_DarkBackground",

					create "ScrollingFrame" {
						Name = "Scroll",
						CanvasSize = UDim2.fromScale(0, 0),

						create "TextBox" {
							Name = "QueryInput",
							Text = "",

							"JEXP_Text",
							"JEXP_DefaultText",
						},
					},
					create "ImageButton" {
						Name = "SelectQueried",

						"JEXP_Hover",
						"JEXP_MonochromeImage"
					},
					create "TextLabel" {
						Name = "QueryCount",

						"JEXP_Text"
					}
				}
			}
		}
	end,
	InstanceFrame = function()
		return create "Frame" {
			Name = "InstanceFrame",
			"JEXP_Background",
			"JEXP_ExplorerFrame",

			create "Frame" {
				Name = "Buffer",
			},
			--create "Frame" {
			--	Name = "Ruler"
			--},
			create "Frame" {
				Name = "Main",

				create "Frame" {
					Name = "Overlay",

					create "ImageButton" {
						Name = "Select",
					}
				},
				create "ImageLabel" {
					Name = "ClassIcon",
				},
				create "ImageLabel" {
					Name = "PackageLinkIcon",
					Visible = false
				},
				create "ImageLabel" {
					Name = "PendingChanges",
					Visible = false
				},
				create "TextBox" {
					Name = "InstanceName",

					Interactable = false,
					TextEditable = false,
				},
				create "ImageButton" {
					Name = "Arrow",
					"JEXP_Arrow"
				},
				create "ImageButton" {
					Name = "Insert",
					Visible = false,

					"JEXP_MonochromeImage"
				},
				create "ImageButton" {
					Name = "Reclass",
					Visible = false,

					"JEXP_MonochromeImage"
				}
			}
		}
	end,
	EditorFrame = function()
		return create "Frame" {
			Size = UDim2.new(1, 0, 1, 0),

			"JEXP_Background",

			create "ScrollingFrame" {
				Name = "Items",
				AnchorPoint = Vector2.new(0.5, 1),
				BackgroundTransparency = 1,
				Position = UDim2.new(0.5, 0, 1, 0),
				Size = UDim2.new(1, 0, 1, 0),
				ZIndex = 5,
				AutomaticCanvasSize = Enum.AutomaticSize.XY,
				BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
				TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
				MidImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
				ElasticBehavior = Enum.ElasticBehavior.Never,
				HorizontalScrollBarInset = Enum.ScrollBarInset.Always,
				VerticalScrollBarInset = Enum.ScrollBarInset.Always,

				"JEXP_DarkBackground",
				"JEXP_ScrollingFrame",

				create "Frame" {
					Name = "Content",
					Size = UDim2.fromScale(0, 0),
					AutomaticSize = Enum.AutomaticSize.XY,
					BackgroundTransparency = 1,

					create "UIListLayout" {
						ItemLineAlignment = Enum.ItemLineAlignment.Start,
						SortOrder = Enum.SortOrder.LayoutOrder,
					}
				},
				create "Frame" {
					Name = "Overlay",
					Size = UDim2.fromScale(1, 1),
					ZIndex = 100,
					BackgroundTransparency = 1
				}
			},
			create "Frame" {
				Name = "ScrollBackgroundHorizontal",
				ZIndex = 4,

				"JEXP_DarkBackground",
				"JEXP_Border",
				"JEXP_ScrollBackgroundHorizontal",
			},
			create "Frame" {
				Name = "ScrollBackgroundVertical",
				ZIndex = 4,

				"JEXP_DarkBackground",
				"JEXP_Border",
				"JEXP_ScrollBackgroundVertical"
			},
		}
	end,
	TabFrame = function()
		return create "Frame" {
			Name = "InstanceTab",
			Size = UDim2.new(0, 70, 1, 0),

			"JEXP_TabFrame",

			create "TextBox" {
				Name = "NameBox",
				Size = UDim2.new(0, 0, 1, 0),
				"JEXP_Text"
			},
			create "TextButton" {
				Name = "Button",
			},
			create "ImageLabel" {
				Name = "Icon"
			}
		}
	end,
}


--!strict

export type Disconnect = () -> ()

local create
local __extend
local __itemgroup
export type ItemGroup<T = any> = {
	add: (ItemGroup<T>, item: T) -> Disconnect,
	add_many: (ItemGroup<T>, items: {T}) -> Disconnect,
	free: (ItemGroup<T>) -> (),
	extend: typeof(__extend),

	items: {T},
	cleanup_handler: (T) -> (),

	parent: typeof(__itemgroup)?,
	children: {[typeof(__itemgroup)]: boolean},
}

__itemgroup = (nil::any)::ItemGroup<any>
function __extend<T>(self: any, new_cleanup_handler: (T) -> (), items: {T}?): ItemGroup<T> return nil::any end

local function group_add(self: ItemGroup, item: any)
	local items = self.items
	table.insert(items, item)

	return function()
		local idx = table.find(items, item)
		if idx then table.remove(items, idx) end
	end
end

local function group_add_many(self: ItemGroup, src: any)
	local items = self.items

	table.move(src, 1, #src, #items + 1, items)
	local clone = table.clone(src)
	return function()
		for _, item in clone do
			local idx = table.find(items, item)
			if idx then table.remove(items, idx) end
		end
	end
end

local function group_free(self: ItemGroup)
	local items = self.items
	local cleanup_handler = self.cleanup_handler
	local children = self.children

	for child: any in children do
		child:free()
	end
	for _, item in items do
		cleanup_handler(item)
	end
	local parent = self.parent
	if parent then
		parent.children[self] = nil
	end
	table.clear(items)
end

local function group_extend<A>(self: ItemGroup, new_cleanup_handler: any, items)
	local new = create(new_cleanup_handler, items)
	new.parent = self
	self.children[new] = true
	return new
end

function create<T>(cleanup_handler: (T) -> (), items: {T}?): ItemGroup<T>
	local items: {T} = if items ~= nil then items else {}

	return ({
		items = items,
		children = {},
		cleanup_handler = cleanup_handler,

		free = group_free,
		add = group_add,
		add_many = group_add_many,
		extend = group_extend,
	}::ItemGroup)::any
end

return {
	create = create,
	cleanup = {
		disconnect = function(c: RBXScriptConnection)
			c:Disconnect()
		end,
		destroy = game.Destroy,
		call = function(f: () -> ())
			f()
		end,
	}
}


type PluginColorPicker = {
	Prompt : (self : PluginColorPicker, startingColor : Color3?) -> Color3?,
	BindToChange : (self : PluginColorPicker, ((Color3) -> ())?) -> (),
	BindToConfirm: (self : PluginColorPicker, (() -> ())?) -> (),
	BindToCancel : (self : PluginColorPicker, (() -> ())?) -> (),
	Set : (self : PluginColorPicker, color : Color3) -> (),
	Cancel : (self : PluginColorPicker, Color3?) -> (),
	Show : (self : PluginColorPicker) -> (),
	Close : (self : PluginColorPicker) -> (),
	Destroy : (self : PluginColorPicker) -> (),
	IsOpened : (self : PluginColorPicker) -> boolean,
}

local RunService = game:GetService('RunService')

local PluginColorPicker = {}
PluginColorPicker.__index = PluginColorPicker

local studioSettings = settings().Studio
local themeChanged = studioSettings.ThemeChanged

local function isRight359(str : string) : boolean
	local num : number? = tonumber(str)

	if num == 0 and string.len(str) > 1 then return false end

	return (num and num <= 359 and num >= 0)
end

local function isRight255(str : string) : boolean
	local num : number? = tonumber(str)

	if num == 0 and string.len(str) > 1 then return false end

	return (num and num <= 255 and num >= 0)
end

local function isRightHex(str : string) : boolean

	if str == '' then return true end

	local signString : string = string.sub(str, 1, 1)
	local numberString : string = string.sub(str, 2, 7)
	local hexMatch : string = string.match(numberString, '%x+')
	local isHexString : boolean = hexMatch == numberString and string.len(str) <= 7

	if signString == '#' and (numberString == '' or isHexString) then
		return true
	end

	return false
end

local function Get255RGB(color : Color3) : (number, number, number)
	return math.round(color.R * 255), math.round(color.G * 255), math.round(color.B * 255)
end

local function Color3FuzzyEq(color1 : Color3, color2 : Color3) : boolean
	local r1 : number, g1 : number, b1 : number = Get255RGB(color1)
	local r2 : number, g2 : number, b2 : number = Get255RGB(color2)

	local rRight : boolean, gRight : boolean, bRight : boolean = r1 == r2, g1 == g2, b1 == b2

	return rRight and gRight and bRight
end

local function SafeFromHex(str : string) : Color3?
	local success : boolean, color : Color3 = pcall(Color3.fromHex, str)

	if success then return color end
end

local function DisconnectTextboxInfo(textboxInfo)
	textboxInfo.textCon:Disconnect()
	textboxInfo.cursorCon:Disconnect()
	textboxInfo.selectionCon:Disconnect()
end

local function ConnectTextboxInfo(textboxInfo)

	local textboxBackground = textboxInfo.textboxBackground
	local textbox = textboxBackground.Textbox

	textboxInfo.olderCursor = textbox.CursorPosition
	textboxInfo.olderSelection = textbox.SelectionStart

	textboxInfo.oldCursor = textbox.CursorPosition
	textboxInfo.oldSelection = textbox.SelectionStart

	textboxInfo.textCon = textbox:GetPropertyChangedSignal('Text'):Connect(function()
		textboxInfo.textFunction()

		if textbox.Text ~= '' then
			textboxInfo.savedValue = textbox.Text
		end

		textboxInfo.oldValue = textbox.Text
	end)

	textboxInfo.cursorCon = textbox:GetPropertyChangedSignal('CursorPosition'):Connect(function()
		textboxInfo.olderCursor = textboxInfo.oldCursor
		textboxInfo.oldCursor = textbox.CursorPosition
	end)

	textboxInfo.selectionCon = textbox:GetPropertyChangedSignal('SelectionStart'):Connect(function()
		textboxInfo.olderSelection = textboxInfo.oldSelection
		textboxInfo.oldSelection = textbox.SelectionStart
	end)
end

local function WrapTopBottomBorders(guiObject)
	local TopBorder = Instance.new('Frame')
	TopBorder.ZIndex = 2
	TopBorder.Position = UDim2.new(0, -1, 0, -1)
	TopBorder.Size = UDim2.new(1, 1, 1, 1)
	TopBorder.BorderSizePixel = 0
	TopBorder.Name = 'TopBorder'

	local BottomBorder = Instance.new('Frame')
	BottomBorder.Name = 'BottomBorder'
	BottomBorder.ZIndex = 1
	BottomBorder.Position = UDim2.new(0, -1, 0, -1)
	BottomBorder.Size = UDim2.new(1, 2, 1, 2)
	BottomBorder.BorderSizePixel = 0

	TopBorder.Parent = guiObject
	BottomBorder.Parent = guiObject
end

local function WrapTextboxBackground(textboxBackground, textboxLabelText)
	local TextboxPadding = Instance.new('UIPadding')
	TextboxPadding.PaddingLeft = UDim.new(0, 3)

	local TextLabelPadding = Instance.new("UIPadding")
	TextLabelPadding.PaddingRight = UDim.new(0, 10)

	local TextLabel = Instance.new("TextLabel")
	TextLabel.AnchorPoint = Vector2.new(1, 0)
	TextLabel.BackgroundTransparency = 1
	TextLabel.Size = UDim2.new(0, 50, 0, 18)
	TextLabel.Text = textboxLabelText
	TextLabel.TextXAlignment = Enum.TextXAlignment.Right
	TextLabel.Font = Enum.Font.SourceSans
	TextLabel.TextSize = 14

	TextLabelPadding.Parent = TextLabel
	TextLabel.Parent = textboxBackground
	TextboxPadding.Parent = textboxBackground.Textbox
end

local function WrapTextboxArrows(textbox)
	local upArrow = Instance.new('ImageButton')
	upArrow.BackgroundTransparency = 1
	upArrow.BorderSizePixel = 0
	upArrow.AnchorPoint = Vector2.new(1, 0)
	upArrow.Position = UDim2.new(1, -1, 0, 1)
	upArrow.Rotation = 1
	upArrow.Size = UDim2.fromOffset(16, 8)
	upArrow.Image = 'rbxassetid://129502343292983'
	upArrow.ZIndex = 12
	upArrow.ScaleType = Enum.ScaleType.Fit
	upArrow.ResampleMode = Enum.ResamplerMode.Default
	upArrow.BackgroundColor3 = Color3.new(1, 1, 1)
	upArrow.ImageColor3 = Color3.new(1, 1, 1)
	upArrow.BorderSizePixel = 0
	upArrow.AutoButtonColor = false
	upArrow.Name = 'UpArrow'

	local downArrow = Instance.new('ImageButton')
	downArrow.BackgroundTransparency = 1
	downArrow.BorderSizePixel = 0
	downArrow.AnchorPoint = Vector2.new(1, 1)
	downArrow.Position = UDim2.new(1, -1, 1, -2)
	downArrow.Rotation = 180
	downArrow.Size = UDim2.fromOffset(16, 8)
	downArrow.Image = 'rbxassetid://129502343292983'
	downArrow.ScaleType = Enum.ScaleType.Fit
	downArrow.ResampleMode = Enum.ResamplerMode.Default
	downArrow.ZIndex = 12
	downArrow.BackgroundColor3 = Color3.new(1, 1, 1)
	downArrow.ImageColor3 = Color3.new(1, 1, 1)
	downArrow.BorderSizePixel = 0
	downArrow.AutoButtonColor = false
	downArrow.Name = 'DownArrow'

	downArrow.Parent = textbox
	upArrow.Parent = textbox
end

local function CreateTextboxForBackground(textboxBackground)
	local textbox = Instance.new("TextBox")
	textbox.Name = 'Textbox'
	textbox.ClearTextOnFocus = false
	textbox.Font = Enum.Font.SourceSans
	textbox.TextSize = 14
	textbox.TextXAlignment = Enum.TextXAlignment.Left
	textbox.ZIndex = 11
	textbox.Position = UDim2.new()
	textbox.Size = UDim2.new(0, 24, 1, 0)
	textbox.BackgroundTransparency = 1
	textbox.Parent = textboxBackground
end

function PluginColorPicker.new(plugin: Plugin, pluginGuiId : string) : PluginColorPicker
	assert(pluginGuiId, 'You should specify pluginGuiId as first argument when creating Color Picker!')

	local self = setmetatable({}, PluginColorPicker)

	local dockWidgetPluginGui = plugin:CreateDockWidgetPluginGuiAsync(pluginGuiId, DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, true, 287, 419))
	dockWidgetPluginGui.Title = 'Select Color'

	-- create gui objects

	local Background = Instance.new("Frame")
	Background.Name = 'Background'
	Background.BackgroundTransparency = 1
	Background.AnchorPoint = Vector2.new(0.5, 0.5)
	Background.Position = UDim2.fromScale(0.5, 0.5)
	Background.Size = UDim2.fromOffset(287, 399)
	Background.BackgroundColor3 = Color3.new(1, 1, 1)

	local OKButton = Instance.new("TextButton")
	OKButton.Name = 'OKButton'
	OKButton.Text = 'OK'
	OKButton.AnchorPoint = Vector2.new(1, 1)
	OKButton.AutoButtonColor = false
	OKButton.BorderSizePixel = 1
	OKButton.Position = UDim2.new(1, -93, 1, -12)
	OKButton.Size = UDim2.new(0, 73, 0, 23)
	OKButton.ZIndex = 11
	OKButton.Font = Enum.Font.SourceSans
	OKButton.TextSize = 14
	OKButton.Parent = Background

	local CancelButton = Instance.new("TextButton")
	CancelButton.Name = 'CancelButton'
	CancelButton.Text = 'Cancel'
	CancelButton.AnchorPoint = Vector2.new(1, 1)
	CancelButton.AutoButtonColor = false
	CancelButton.BorderSizePixel = 1
	CancelButton.Position = UDim2.new(1, -12, 1, -12)
	CancelButton.Size = UDim2.new(0, 73, 0, 23)
	CancelButton.ZIndex = 11
	CancelButton.Font = Enum.Font.SourceSans
	CancelButton.TextSize = 14
	CancelButton.Parent = Background

	local Hex = Instance.new("Frame")
	Hex.Name = 'Hex'
	Hex.AnchorPoint = Vector2.new(1, 1)
	Hex.BorderSizePixel = 2
	Hex.Position = UDim2.new(1, -68, 1, -50)
	Hex.Size = UDim2.new(0, 91, 0, 19)
	Hex.ZIndex = 11
	Hex.Parent = Background

	local HexTextboxUIStroke = Instance.new('UIStroke')
	HexTextboxUIStroke.LineJoinMode = Enum.LineJoinMode.Miter
	HexTextboxUIStroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
	HexTextboxUIStroke.Parent = Hex

	local Blue = Instance.new('Frame')
	Blue.Name = 'Blue'
	Blue.AnchorPoint = Vector2.new(1, 1)
	Blue.BorderSizePixel = 1
	Blue.Position = UDim2.new(1, -18, 1, -82)
	Blue.Size = UDim2.new(0, 41, 0, 19)
	Blue.ZIndex = 11
	Blue.Parent = Background

	local Green = Instance.new("Frame")
	Green.Name = 'Green'
	Green.AnchorPoint = Vector2.new(1, 1)
	Green.BorderSizePixel = 1
	Green.Position = UDim2.new(1, -18, 1, -109)
	Green.Size = UDim2.new(0, 41, 0, 19)
	Green.ZIndex = 11
	Green.Parent = Background

	local Red = Instance.new("Frame")
	Red.Name = 'Red'
	Red.AnchorPoint = Vector2.new(1, 1)
	Red.BorderSizePixel = 1
	Red.Position = UDim2.new(1, -18, 1, -136)
	Red.Size = UDim2.new(0, 41, 0, 19)
	Red.ZIndex = 11
	Red.Parent = Background

	local Val = Instance.new('Frame')
	Val.Name = 'Val'
	Val.AnchorPoint = Vector2.new(1, 1)
	Val.BorderSizePixel = 1
	Val.Position = UDim2.new(1, -119, 1, -82)
	Val.Size = UDim2.new(0, 41, 0, 19)
	Val.ZIndex = 11
	Val.Parent = Background

	local Sat = Instance.new('Frame')
	Sat.Name = 'Sat'
	Sat.AnchorPoint = Vector2.new(1, 1)
	Sat.BorderSizePixel = 1
	Sat.Position = UDim2.new(1, -119, 1, -109)
	Sat.Size = UDim2.new(0, 41, 0, 19)
	Sat.ZIndex = 11
	Sat.Parent = Background

	local Hue = Instance.new('Frame')
	Hue.Name = 'Hue'
	Hue.AnchorPoint = Vector2.new(1, 1)
	Hue.BorderSizePixel = 1
	Hue.Position = UDim2.new(1, -119, 1, -136)
	Hue.Size = UDim2.new(0, 41, 0, 19)
	Hue.ZIndex = 11
	Hue.Parent = Background

	local ColorShow = Instance.new("Frame")
	ColorShow.Name = 'ColorShow'
	ColorShow.AnchorPoint = Vector2.new(0, 1)
	ColorShow.BorderSizePixel = 0
	ColorShow.Position = UDim2.new(0, 13, 1, -49)
	ColorShow.Size = UDim2.new(0, 58, 0, 106)
	ColorShow.ZIndex = 3
	ColorShow.Parent = Background

	local ColorChoose = Instance.new('Frame')
	ColorChoose.Name = 'ColorChoose'
	ColorChoose.BorderSizePixel = 0
	ColorChoose.Position = UDim2.new(0, 13, 0, 21)
	ColorChoose.Size = UDim2.new(0, 220, 0, 200)
	ColorChoose.BackgroundColor3 = Color3.new(1, 1, 1)
	ColorChoose.ZIndex = 3
	ColorChoose.Parent = Background

	local WhiteOverlay = Instance.new("Frame")
	WhiteOverlay.Name = 'WhiteOverlay'
	WhiteOverlay.BorderSizePixel = 0
	WhiteOverlay.Size = UDim2.new(1, 0, 1, 0)
	WhiteOverlay.ZIndex = 4
	WhiteOverlay.ClipsDescendants = true
	WhiteOverlay.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
	WhiteOverlay.Parent = ColorChoose

	local ColorChooseCursor = Instance.new('ImageLabel')
	ColorChooseCursor.Name = 'Cursor'
	ColorChooseCursor.BackgroundTransparency = 1
	ColorChooseCursor.Image = 'http://www.roblox.com/asset/?id=78763415020111'
	ColorChooseCursor.ImageColor3 = Color3.fromRGB(0, 0, 0)
	ColorChooseCursor.AnchorPoint = Vector2.new(0.5, 0.5)
	ColorChooseCursor.Size = UDim2.new(0, 20, 0, 20)
	ColorChooseCursor.ZIndex = 5
	ColorChooseCursor.Parent = WhiteOverlay

	local WhiteOverlayGradient = Instance.new("UIGradient")
	WhiteOverlayGradient.Rotation = 90
	WhiteOverlayGradient.Transparency = NumberSequence.new({
		NumberSequenceKeypoint.new(0, 1, 0),
		NumberSequenceKeypoint.new(1, 0, 0)
	})

	local ColorChooseGradient = Instance.new('UIGradient')
	ColorChooseGradient.Color = ColorSequence.new({
		ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 0, 0)),
		ColorSequenceKeypoint.new(0.167, Color3.fromRGB(255, 0, 255)),
		ColorSequenceKeypoint.new(0.333, Color3.fromRGB(0, 0, 255)),
		ColorSequenceKeypoint.new(0.5, Color3.fromRGB(0, 255, 255)),
		ColorSequenceKeypoint.new(0.667, Color3.fromRGB(0, 255, 0)),
		ColorSequenceKeypoint.new(0.833, Color3.fromRGB(255, 255, 0)),
		ColorSequenceKeypoint.new(1, Color3.fromRGB(255, 0, 0))
	})

	WhiteOverlayGradient.Parent = WhiteOverlay
	ColorChooseGradient.Parent = ColorChoose

	local ValueChoose = Instance.new('Frame')
	ValueChoose.Name = 'ValueChoose'
	ValueChoose.ZIndex = 3
	ValueChoose.BorderSizePixel = 0
	ValueChoose.AnchorPoint = Vector2.new(1, 0)
	ValueChoose.Position = UDim2.new(1, -23, 0, 21)
	ValueChoose.Size = UDim2.new(0, 13, 0, 200)
	ValueChoose.BackgroundColor3 = Color3.new(1, 1, 1)
	ValueChoose.Parent = Background

	local ValueChooseGradient = Instance.new("UIGradient")
	ValueChooseGradient.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, Color3.new(1, 1, 1)), ColorSequenceKeypoint.new(1, Color3.new(0, 0, 0))})
	ValueChooseGradient.Rotation = 90
	ValueChooseGradient.Parent = ValueChoose

	local ValueChooseCursor = Instance.new('ImageLabel')
	ValueChooseCursor.Name = 'Cursor'
	ValueChooseCursor.AnchorPoint = Vector2.new(0, 0.5)
	ValueChooseCursor.BackgroundTransparency = 1
	ValueChooseCursor.Size = UDim2.new(0, 5, 0, 9)
	ValueChooseCursor.Image = 'http://www.roblox.com/asset/?id=133922388288237'
	ValueChooseCursor.Position = UDim2.new(1, 1, 0, 0)
	ValueChooseCursor.Parent = ValueChoose

	local ValueChooseHitbox = Instance.new('TextButton')
	ValueChooseHitbox.ZIndex = 11
	ValueChooseHitbox.Transparency = 1
	ValueChooseHitbox.Name = 'Hitbox'
	ValueChooseHitbox.Position = UDim2.fromScale(0, 1)
	ValueChooseHitbox.Size = UDim2.new(1, 6, 1, 4)
	ValueChooseHitbox.AnchorPoint = Vector2.new(0, 1)
	ValueChooseHitbox.BackgroundColor3 = Color3.new(1, 1, 1)
	ValueChooseHitbox.Parent = ValueChoose

	local ColorChooseHitbox = Instance.new('TextButton')
	ColorChooseHitbox.ZIndex = 11
	ColorChooseHitbox.Transparency = 1
	ColorChooseHitbox.Name = 'Hitbox'
	ColorChooseHitbox.Size = UDim2.fromScale(1, 1)
	ColorChooseHitbox.Position = UDim2.new()
	ColorChooseHitbox.BackgroundColor3 = Color3.new(1, 1, 1)
	ColorChooseHitbox.Parent = ColorChoose

	local MainHitbox = Instance.new('TextButton')
	MainHitbox.Active = false
	MainHitbox.ZIndex = 10
	MainHitbox.Transparency = 1
	MainHitbox.Name = 'MainHitbox'
	MainHitbox.Size = UDim2.fromScale(1, 1)
	MainHitbox.Position = UDim2.new()
	MainHitbox.Parent = dockWidgetPluginGui

	CreateTextboxForBackground(Hex)
	CreateTextboxForBackground(Red)
	CreateTextboxForBackground(Blue)
	CreateTextboxForBackground(Green)
	CreateTextboxForBackground(Hue)
	CreateTextboxForBackground(Sat)
	CreateTextboxForBackground(Val)

	Hex.Textbox.Text = '#ffffff'
	Hex.Textbox.Size = UDim2.new(1, 0, 1, 0)

	Red.Textbox.Text = '255'
	Blue.Textbox.Text = '255'
	Green.Textbox.Text = '255'
	Hue.Textbox.Text = '0'
	Sat.Textbox.Text = '255'
	Val.Textbox.Text = '255'

	--

	-- set selfs

	self.isOpened = false

	self.DockWidgetPluginGui = dockWidgetPluginGui
	self.Background = Background

	--

	-- connections

	local runCon = nil

	self.cons = {
		self.DockWidgetPluginGui:BindToClose(function()
			if self.onCancel then self.onCancel() end
		end),

		self.Background.OKButton.MouseButton1Up:Once(function()
			if self.onConfirm then self.onConfirm() end
		end),

		self.Background.CancelButton.MouseButton1Up:Once(function()
			if self.onCancel then self.onCancel() end
		end),

		themeChanged:Connect(function()
			self:UpdateTheme()
		end),

		MainHitbox.MouseEnter:Connect(function()
			if runCon then runCon:Disconnect() end
		end),

		MainHitbox.MouseLeave:Connect(function()
			if runCon then runCon:Disconnect() end
		end),

		MainHitbox.MouseButton1Up:Connect(function()
			if runCon then runCon:Disconnect() end
		end),

		CancelButton.MouseButton1Up:Connect(function()
			if runCon then runCon:Disconnect() end
		end),

		OKButton.MouseButton1Up:Connect(function()
			if runCon then runCon:Disconnect() end
		end),

		ColorChooseHitbox.MouseButton1Up:Connect(function()
			if runCon then runCon:Disconnect() end
		end),

		ValueChooseHitbox.MouseButton1Up:Connect(function()
			if runCon then runCon:Disconnect() end
		end),

		ColorChooseHitbox.MouseButton1Down:Connect(function()
			if runCon then runCon:Disconnect() end

			local lastMousePosition : Vector2 = Vector2.new()

			runCon = RunService.RenderStepped:Connect(function()
				local currentMousePosition : Vector2 = dockWidgetPluginGui:GetRelativeMousePosition()
				local satTextbox = self.Background.Sat.Textbox
				local hueTextbox = self.Background.Hue.Textbox

				if currentMousePosition ~= lastMousePosition then

					local normalOffset : Vector2 = currentMousePosition - self.Background.ColorChoose.AbsolutePosition
					local offsetX : number = math.clamp(normalOffset.X, 0, self.Background.ColorChoose.AbsoluteSize.X)
					local offsetY : number = math.clamp(normalOffset.Y, 0, self.Background.ColorChoose.AbsoluteSize.Y)

					local newHue : number = offsetX / self.Background.ColorChoose.AbsoluteSize.X
					local newSat : number = offsetY / self.Background.ColorChoose.AbsoluteSize.Y

					satTextbox.Text = tonumber(math.round((1 - newSat) * 255))

					task.defer(function() -- very dumb fix
						hueTextbox.Text = tonumber(math.round((1 - newHue) * 359))
					end)

					lastMousePosition = currentMousePosition
				end
			end)
		end),

		ValueChooseHitbox.MouseButton1Down:Connect(function()
			if runCon then runCon:Disconnect() end

			local lastMousePosition : Vector2 = Vector2.new()

			runCon = RunService.RenderStepped:Connect(function()
				local currentMousePosition : Vector2 = dockWidgetPluginGui:GetRelativeMousePosition()
				local valTextbox = self.Background.Val.Textbox

				if currentMousePosition ~= lastMousePosition then

					local normalOffsetY : number = currentMousePosition.Y - self.Background.ValueChoose.AbsolutePosition.Y
					local offsetY : number = math.clamp(normalOffsetY, 0, self.Background.ValueChoose.AbsoluteSize.Y)

					local newVal : number = offsetY / self.Background.ValueChoose.AbsoluteSize.Y

					valTextbox.Text = tostring(math.round((1 - newVal) * 255))

					lastMousePosition = currentMousePosition
				end
			end)
		end),

	}

	--

	-- textboxes wrap!

	local hexTextboxInfo = {textboxBackground = Hex}
	hexTextboxInfo.textFunction = function()
		if isRightHex(Hex.Textbox.Text) then
			local fromHex : Color3? = SafeFromHex(Hex.Textbox.Text)

			if fromHex then self:Set(fromHex) end
		else
			DisconnectTextboxInfo(hexTextboxInfo)

			Hex.Textbox.Text = hexTextboxInfo.oldValue
			Hex.Textbox.CursorPosition = hexTextboxInfo.olderCursor
			Hex.Textbox.SelectionStart = hexTextboxInfo.olderSelection

			ConnectTextboxInfo(hexTextboxInfo)
		end
	end

	local hueTextboxInfo = {textboxBackground = Hue, maxValue = '359'}
	hueTextboxInfo.textFunction = function()

		if Hue.Textbox.Text == '' then return end

		local hue : number, sat : number, val : number = self.color:ToHSV()

		if isRight359(Hue.Textbox.Text) then
			self:Set(Color3.fromHSV(tonumber(Hue.Textbox.Text) / 359, sat, val))
		else
			DisconnectTextboxInfo(hueTextboxInfo)

			Hue.Textbox.Text = hueTextboxInfo.oldValue
			Hue.Textbox.CursorPosition = hueTextboxInfo.olderCursor
			Hue.Textbox.SelectionStart = hueTextboxInfo.olderSelection

			ConnectTextboxInfo(hueTextboxInfo)
		end
	end

	local satTextboxInfo = {textboxBackground = Sat, maxValue = '255'}
	satTextboxInfo.textFunction = function()

		if Sat.Textbox.Text == '' then return end

		local hue : number, sat : number, val : number = self.color:ToHSV()

		if isRight255(Sat.Textbox.Text) then
			self:Set(Color3.fromHSV(tonumber(Hue.Textbox.Text) / 359, tonumber(Sat.Textbox.Text) / 255, val))
		else
			DisconnectTextboxInfo(satTextboxInfo)

			Sat.Textbox.Text = satTextboxInfo.oldValue
			Sat.Textbox.CursorPosition = satTextboxInfo.olderCursor
			Sat.Textbox.SelectionStart = satTextboxInfo.olderSelection

			ConnectTextboxInfo(satTextboxInfo)
		end
	end

	local valTextboxInfo = {textboxBackground = Val, maxValue = '255'}
	valTextboxInfo.textFunction = function()

		if Val.Textbox.Text == '' then return end

		if isRight255(Val.Textbox.Text) then
			self:Set(Color3.fromHSV(tonumber(Hue.Textbox.Text / 359), tonumber(Sat.Textbox.Text) / 255, tonumber(Val.Textbox.Text) / 255))
		else
			DisconnectTextboxInfo(valTextboxInfo)

			Val.Textbox.Text = valTextboxInfo.oldValue
			Val.Textbox.CursorPosition = valTextboxInfo.olderCursor
			Val.Textbox.SelectionStart = valTextboxInfo.olderSelection

			ConnectTextboxInfo(valTextboxInfo)
		end
	end

	local redTextboxInfo = {textboxBackground = Red, maxValue = '255'}
	redTextboxInfo.textFunction = function()

		if Red.Textbox.Text == '' then return end

		local r : number, g : number, b : number = Get255RGB(self.color)

		if isRight255(Red.Textbox.Text) then
			self:Set(Color3.fromRGB(tonumber(Red.Textbox.Text), g, b))
		else
			DisconnectTextboxInfo(redTextboxInfo)

			Red.Textbox.Text = redTextboxInfo.oldValue
			Red.Textbox.CursorPosition = redTextboxInfo.olderCursor
			Red.Textbox.SelectionStart = redTextboxInfo.olderSelection

			ConnectTextboxInfo(redTextboxInfo)
		end
	end

	local greenTextboxInfo = {textboxBackground = Green, maxValue = '255'}
	greenTextboxInfo.textFunction = function()

		if Green.Textbox.Text == '' then return end

		local r : number, g : number, b : number = Get255RGB(self.color)

		if isRight255(Green.Textbox.Text) then
			self:Set(Color3.fromRGB(r, tonumber(Green.Textbox.Text), b))
		else
			DisconnectTextboxInfo(greenTextboxInfo)

			Green.Textbox.Text = greenTextboxInfo.oldValue
			Green.Textbox.CursorPosition = greenTextboxInfo.olderCursor
			Green.Textbox.SelectionStart = greenTextboxInfo.olderSelection

			ConnectTextboxInfo(greenTextboxInfo)
		end
	end

	local blueTextboxInfo = {textboxBackground = Blue, maxValue = '255'}
	blueTextboxInfo.textFunction = function()

		if Blue.Textbox.Text == '' then return end

		local r : number, g : number, b : number = Get255RGB(self.color)

		if isRight255(Blue.Textbox.Text) then
			self:Set(Color3.fromRGB(r, g, tonumber(Blue.Textbox.Text)))
		else
			DisconnectTextboxInfo(blueTextboxInfo)

			Blue.Textbox.Text = blueTextboxInfo.oldValue
			Blue.Textbox.CursorPosition = blueTextboxInfo.olderCursor
			Blue.Textbox.SelectionStart = blueTextboxInfo.olderSelection

			ConnectTextboxInfo(blueTextboxInfo)
		end
	end

	self.textboxes = {
		hexTextboxInfo,
		hueTextboxInfo,
		valTextboxInfo,
		satTextboxInfo,
		redTextboxInfo,
		greenTextboxInfo,
		blueTextboxInfo
	}

	--

	-- wrap some stuff (Textboxes, Arrows, Bottom and top borders, arrow mechanics)

	WrapTextboxBackground(Blue, 'Blue:')
	WrapTextboxBackground(Red, 'Red:')
	WrapTextboxBackground(Green, 'Green:')
	WrapTextboxBackground(Val, 'Val:')
	WrapTextboxBackground(Sat, 'Sat:')
	WrapTextboxBackground(Hue, 'Hue:')
	WrapTextboxBackground(Hex, 'Hex:')

	WrapTextboxArrows(Blue)
	WrapTextboxArrows(Red)
	WrapTextboxArrows(Green)
	WrapTextboxArrows(Val)
	WrapTextboxArrows(Sat)
	WrapTextboxArrows(Hue)

	WrapTopBottomBorders(ValueChoose)
	WrapTopBottomBorders(ColorChoose)
	WrapTopBottomBorders(ColorShow)

	self.bottomBorders = {
		ValueChoose.BottomBorder,
		ColorChoose.BottomBorder,
		ColorShow.BottomBorder
	}

	self.topBorders = {
		ValueChoose.TopBorder,
		ColorChoose.TopBorder,
		ColorShow.TopBorder
	}

	self.upArrows = {
		Blue.UpArrow,
		Red.UpArrow,
		Green.UpArrow,
		Val.UpArrow,
		Hue.UpArrow,
		Sat.UpArrow
	}

	self.downArrows = {
		Blue.DownArrow,
		Red.DownArrow,
		Green.DownArrow,
		Val.DownArrow,
		Hue.DownArrow,
		Sat.DownArrow
	}

	local lastArrowThread = nil

	for _, textboxInfo in ipairs(self.textboxes) do
		ConnectTextboxInfo(textboxInfo)

		local textboxBackground = textboxInfo.textboxBackground
		local textbox = textboxBackground.Textbox

		local focusedCon = textbox.Focused:Connect(function()
			self.focusedTextbox = textboxBackground

			textboxBackground.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder, Enum.StudioStyleGuideModifier.Selected)
		end)

		local focusLostCon = textbox.FocusLost:Connect(function()
			if self.focusedTextbox == textboxBackground then self.focusedTextbox = nil end

			textboxBackground.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder, Enum.StudioStyleGuideModifier.Default)

			if textbox.Text == '' and textboxBackground.Name ~= 'Hex' then
				DisconnectTextboxInfo(textboxInfo)

				textbox.Text = textboxInfo.savedValue

				ConnectTextboxInfo(textboxInfo)
			end
		end)

		if textboxBackground == Hex then continue end

		local upArrow = textboxBackground.UpArrow
		local downArrow = textboxBackground.DownArrow

		upArrow.MouseButton1Down:Connect(function()

			textbox:CaptureFocus()
			textbox.CursorPosition = 1

			if textboxInfo.savedValue ~= textboxInfo.maxValue then

				textbox.SelectionStart = string.len(textbox.Text) + 1

				upArrow.BackgroundTransparency = 0
				upArrow.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Selected)

				if lastArrowThread then task.cancel(lastArrowThread) lastArrowThread = nil end

				textbox.Text = tonumber(textbox.Text) + 1

				textbox.SelectionStart = string.len(textbox.Text) + 1

				lastArrowThread = task.delay(0.3, function()

					local total : number = 0

					while true do
						if textboxInfo.savedValue == textboxInfo.maxValue then
							break
						end

						total += RunService.Heartbeat:Wait()

						textbox.Text = tonumber(textbox.Text) + total // 0.1
						total %= 0.1

						textbox.SelectionStart = string.len(textbox.Text) + 1
					end
				end)
			end
		end)

		downArrow.MouseButton1Down:Connect(function()

			textbox:CaptureFocus()
			textbox.CursorPosition = 1

			if textboxInfo.savedValue ~= '0' then

				textbox.SelectionStart = string.len(textbox.Text) + 1

				textbox.SelectionStart = string.len(textbox.Text) + 1

				downArrow.BackgroundTransparency = 0
				downArrow.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Selected)

				if lastArrowThread then task.cancel(lastArrowThread) lastArrowThread = nil end

				textbox.Text = tonumber(textbox.Text) - 1

				lastArrowThread = task.delay(0.3, function()

					local total : number = 0

					while true do
						if textboxInfo.savedValue == '0' then
							break
						end

						total += RunService.Heartbeat:Wait()

						textbox.Text = tonumber(textbox.Text) - total // 0.15
						total %= 0.15

						textbox.SelectionStart = string.len(textbox.Text) + 1
					end
				end)
			end
		end)

		upArrow.MouseButton1Up:Connect(function()
			if lastArrowThread then task.cancel(lastArrowThread) lastArrowThread = nil end
			if runCon then runCon:Disconnect() end
			upArrow.BackgroundTransparency = 0
			upArrow.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Hover)
		end)

		upArrow.MouseEnter:Connect(function()
			upArrow.BackgroundTransparency = 0
			upArrow.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Hover)
		end)

		upArrow.MouseLeave:Connect(function()
			if lastArrowThread then task.cancel(lastArrowThread) lastArrowThread = nil end
			upArrow.BackgroundTransparency = 1
		end)

		downArrow.MouseButton1Up:Connect(function()
			if lastArrowThread then task.cancel(lastArrowThread) lastArrowThread = nil end
			if runCon then runCon:Disconnect() end
			downArrow.BackgroundTransparency = 0
			downArrow.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Hover)
		end)

		downArrow.MouseEnter:Connect(function()
			downArrow.BackgroundTransparency = 0
			downArrow.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Hover)
		end)

		downArrow.MouseLeave:Connect(function()
			if lastArrowThread then task.cancel(lastArrowThread) lastArrowThread = nil end
			downArrow.BackgroundTransparency = 1
		end)

		textboxBackground.MouseEnter:Connect(function()
			if textboxBackground ~= self.focusedTextbox then
				textboxBackground.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder, Enum.StudioStyleGuideModifier.Hover)
			end
		end)

		textboxBackground.MouseLeave:Connect(function()
			if textboxBackground ~= self.focusedTextbox then
				textboxBackground.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder, Enum.StudioStyleGuideModifier.Default)
			end
		end)

		textboxBackground.Textbox:GetPropertyChangedSignal('Text'):Connect(function()
			if textboxInfo.savedValue == '0' then
				textboxBackground.DownArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Disabled)
			else
				textboxBackground.DownArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Default)
			end

			if textboxInfo.savedValue == textboxInfo.maxValue then
				textboxBackground.UpArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Disabled)
			else
				textboxBackground.UpArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Default)
			end
		end)

		OKButton.MouseEnter:Connect(function()
			OKButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Hover)
			OKButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Hover)
		end)

		OKButton.MouseLeave:Connect(function()
			OKButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Default)
			OKButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Default)
		end)

		OKButton.MouseButton1Down:Connect(function()
			OKButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Pressed)
			OKButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Pressed)
		end)

		OKButton.MouseButton1Up:Connect(function()
			OKButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Default)
			OKButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Default)
		end)

		CancelButton.MouseEnter:Connect(function()
			CancelButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Hover)
			CancelButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Hover)
		end)

		CancelButton.MouseLeave:Connect(function()
			CancelButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Default)
			CancelButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Default)
		end)

		CancelButton.MouseButton1Down:Connect(function()
			CancelButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Pressed)
			CancelButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Pressed)
		end)

		CancelButton.MouseButton1Up:Connect(function()
			CancelButton.BackgroundColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Default)
			CancelButton.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder, Enum.StudioStyleGuideModifier.Default)
		end)
	end

	--

	self:Set(Color3.new(1, 1, 1))
	self:UpdateTheme() -- update theme before showing it

	Background.Parent = dockWidgetPluginGui -- set parent of gui to pluginGui

	return self
end

function PluginColorPicker:Set(color : Color3)

	self.color = color

	local hue : number, sat : number, val : number = color:ToHSV()
	local r : number, g : number, b : number = color.R, color.G, color.B
	local hex : string = color:ToHex()

	local colorFromHue : Color3 = Color3.fromHSV(tonumber(self.Background.Hue.Textbox.Text) / 359, sat, val)
	local colorFromSat : Color3 = Color3.fromHSV(hue, tonumber(self.Background.Sat.Textbox.Text) / 255, val)
	local colorFromHex : Color3? = SafeFromHex(self.Background.Hex.Textbox.Text)

	for _, textboxInfo in ipairs(self.textboxes) do
		DisconnectTextboxInfo(textboxInfo)
	end

	if colorFromHex == nil or not Color3FuzzyEq(colorFromHex, color) then
		self.Background.Hex.Textbox.Text = '#'.. color:ToHex()
	end

	if not Color3FuzzyEq(colorFromHue, color) then
		self.Background.Hue.Textbox.Text = math.round(hue * 359)
	end

	if not Color3FuzzyEq(colorFromSat, color) then
		self.Background.Sat.Textbox.Text = math.round(sat * 255)
	end

	self.Background.Val.Textbox.Text = math.round(val * 255)

	self.Background.Red.Textbox.Text = math.round(r * 255)
	self.Background.Green.Textbox.Text = math.round(g * 255)
	self.Background.Blue.Textbox.Text = math.round(b * 255)

	local hueFromText : number = tonumber(self.Background.Hue.Textbox.Text) / 359
	local satFromText : number = tonumber(self.Background.Sat.Textbox.Text) / 255

	self.Background.ColorChoose.WhiteOverlay.Cursor.Position = UDim2.fromScale(1 - hueFromText, 1 - satFromText)
	self.Background.ValueChoose.Cursor.Position = UDim2.new(1, 1, 1 - val, 0)

	self.Background.ColorShow.BackgroundColor3 = color
	self.Background.ValueChoose.UIGradient.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, Color3.fromHSV(hueFromText, satFromText, 1)), ColorSequenceKeypoint.new(1, Color3.new(0, 0, 0))})

	for _, textboxInfo in ipairs(self.textboxes) do
		textboxInfo.oldValue = textboxInfo.textboxBackground.Textbox.Text
		textboxInfo.savedValue = textboxInfo.textboxBackground.Textbox.Text
		ConnectTextboxInfo(textboxInfo)
	end

	if self.onChange then self.onChange(color) end
end

function PluginColorPicker:UpdateTheme()
	local currentTheme = studioSettings.Theme

	-- do a mf buttons and main buttons.....

	pcall(function()
		self.Background.BackgroundColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground)

		self.Background.OKButton.BorderColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder)
		self.Background.CancelButton.BorderColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.ButtonBorder)
		self.Background.OKButton.BackgroundColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.Button)
		self.Background.CancelButton.BackgroundColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.Button)
		self.Background.OKButton.TextColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.ButtonText)
		self.Background.CancelButton.TextColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.ButtonText)

		self.Background.Hex.BorderColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder)
		self.Background.Hex.UIStroke.Color = currentTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground)
		self.Background.Hex.BackgroundColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.InputFieldBackground)
		self.Background.Hex.Textbox.TextColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.MainText)

		self.Background.ValueChoose.Cursor.ImageColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.TitlebarText)

		if self.focusedTextbox then
			self.focusedTextbox.BorderColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder, Enum.StudioStyleGuideModifier.Selected)
		end

		for _, topBorder in ipairs(self.topBorders) do
			topBorder.BackgroundColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.Dark)
		end

		for _, bottomBorder in ipairs(self.bottomBorders) do
			bottomBorder.BackgroundColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.Light)
		end

		for _, textboxInfo in ipairs(self.textboxes) do

			local textboxBackground = textboxInfo.textboxBackground
			local textbox = textboxBackground.Textbox

			textboxBackground.BackgroundColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.InputFieldBackground)
			textboxBackground.BorderColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder)
			textboxBackground.TextLabel.TextColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.TitlebarText)
			textbox.TextColor3 = currentTheme:GetColor(Enum.StudioStyleGuideColor.MainText)

			if textboxBackground.Name == 'Hex' then continue end

			if textboxInfo.savedValue == '0' then
				textboxBackground.DownArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Disabled)
			else
				textboxBackground.DownArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Default)
			end

			if textboxInfo.savedValue == textboxInfo.maxValue then
				textboxBackground.UpArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Disabled)
			else
				textboxBackground.UpArrow.ImageColor3 = studioSettings.Theme:GetColor(Enum.StudioStyleGuideColor.SubText, Enum.StudioStyleGuideModifier.Default)
			end
		end
	end)
end

function PluginColorPicker:Prompt(startingColor : Color3?)
	if startingColor then self:Set(startingColor) end

	self.DockWidgetPluginGui.Enabled = true

	self.running = coroutine.running()

	self.DockWidgetPluginGui:BindToClose(function()
		task.spawn(self.running)
	end)

	local okButtonCon = self.Background.OKButton.MouseButton1Up:Once(function()
		task.spawn(self.running, self.color)
	end)

	local cancelButtonCon = self.Background.CancelButton.MouseButton1Up:Once(function()
		task.spawn(self.running)
	end)

	local result : Color3? = coroutine.yield()

	okButtonCon:Disconnect()
	cancelButtonCon:Disconnect()
	self.DockWidgetPluginGui:BindToClose(nil)
	self.DockWidgetPluginGui.Enabled = false

	self.running = nil
	self.onChange = nil

	return result
end

function PluginColorPicker:Cancel(customColor : Color3?)
	if self.running then task.spawn(self.running, customColor) end
	if self.onCancel then task.spawn(self.onCancel) end
end

function PluginColorPicker:Close()
	self.DockWidgetPluginGui:BindToClose(nil)
	self.DockWidgetPluginGui.Enabled = false
end

function PluginColorPicker:BindToChange(fn :( (Color3) -> ...any)?)
	self.onChange = fn
end

function PluginColorPicker:BindToConfirm(fn)
	self.onConfirm = fn
end

function PluginColorPicker:BindToCancel(fn)
	self.onCancel = fn
end

function PluginColorPicker:IsOpened()
	return self.DockWidgetPluginGui.Enabled
end

function PluginColorPicker:Show()
	self.DockWidgetPluginGui.Enabled = true
end

function PluginColorPicker:Destroy()
	for _, con in ipairs(self.cons) do
		con:Disconnect()
	end

	self.Background:Destroy()
end

return PluginColorPicker


--[[
	This module is distributed under a modified MIT license

	Copyright (c) 2025 athar_adv

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
]]

return nil


--!strict

export type SignalConnection = {
	Disconnect: typeof(
		--[[
			Disconnects the connection from the event.
		]]
		function(self: SignalConnection)end
	),
	Connected: boolean,

	-- Private members
	[{}]: {
		MainThread: thread?,
		Fn: (...any) -> (),
		Connections: {SignalConnection},
		Index: number,
		IsParallel: boolean
	}
}

-- Function types are made using typeof() to preserve doc comments
export type SimpleSignal<T...> = {
	Destroy: typeof(
		--[[
		Destroys this event object.
		]]
		function(self: SimpleSignal<T...>): () end
	),
	Connect: typeof(
		--[[
		Connects the given function to the event and returns an SignalConnection that represents it.

		fn: The function to connect to the event.
		]]
		function(self: SimpleSignal<T...>, fn: (T...) -> ()): SignalConnection
			return {}::SignalConnection
		end
	),
	ConnectParallel: typeof(
		--[[
		Connects the given function to the event (running it in a seperate core) and returns an SignalConnection that represents it.

		fn: The function to connect to the event.
		]]
		function(self: SimpleSignal<T...>, fn: (T...) -> ()): SignalConnection
			return {}::SignalConnection
		end
	),
	Once: typeof(
		--[[
		Connects the given function to the event (for a single invocation) and returns an SignalConnection that represents it.

		fn: The function to connect to the event.
		]]
		function(self: SimpleSignal<T...>, fn: (T...) -> ()): SignalConnection
			return {}::SignalConnection
		end
	),
	Fire: typeof(
		--[[
		Upon firing, all SignalConnections connected to this event will be called.

		T...: The arguments to pass to connected functions.
		]]
		function(self: SimpleSignal<T...>, ...:T...): () end
	),
	DisconnectAll: typeof(
		--[[
		Disconnects all SignalConnections currently connected to the event.
		]]
		function(self: SimpleSignal<T...>): () end
	),
	Wait: typeof(
		--[[
		Yields the current thread until the signal fires
		and returns the arguments provided by the signal.
		]]
		function(self: SimpleSignal<T...>): T... end
	),

	Connections: {SignalConnection},
	Destroyed: boolean
}

return nil


--!optimize 2
--!strict
--!native

--[[ SimpleSignal ]]

-- Author: athar_adv

local Types = require(script.Types)

export type SignalConnection = Types.SignalConnection
export type SimpleSignal<T...=(any)> = Types.SimpleSignal<T...>

export type RBXScriptConnection = SignalConnection
export type RBXScriptSignal<T...=(any)> = SimpleSignal<T...>

-- Key for accessing the private field of connections
local CONNECTION_PRIVATE = {}

local T_ASSERT_FORMAT = "Expected '%s' for '%s', got '%s'"
local DESTROYED_MSG = "Cannot use a destroyed signal."
local SUSPENDED = "suspended"

-- ConnectParallel will error if not called from an Actor context
local IS_PARALLEL_CONTEXT = pcall(function()
	local c = game.Destroying:ConnectParallel(function()end)
	c:Disconnect()
end)

local function call(c: SignalConnection, ...)
	local private = c[CONNECTION_PRIVATE]

	local parallel = private.IsParallel
	local old = private.MainThread
	private.MainThread = nil

	if parallel then task.desynchronize() end
	private.Fn(...)
	if parallel then task.synchronize() end

	if c.Connected then
		private.MainThread = old
	end
end

local function mainThreadWorker(c: SignalConnection, a, ...)
	-- Initial call if arguments are provided (so coroutine.resume doesn't have to be called twice)
	if a then
		call(c, a, ...)
	end

	while c.Connected do
		call(c, coroutine.yield())
	end
end

local function connection_disconnect(self: SignalConnection): ()
	if not self.Connected then return end
	self.Connected = false
	local private = self[CONNECTION_PRIVATE]

	local t = private.MainThread
	if t then
		task.cancel(t)
	end
	private.MainThread = nil

	local connections = private.Connections
	local l = #connections
	local pos = private.Index

	if l == pos then
		connections[l] = nil
		return
	end
	local last = connections[l]
	last[CONNECTION_PRIVATE].Index = pos

	connections[pos], connections[l] = last, nil
end

local function signal_connect<T...>(self: SimpleSignal<T...>, fn: (T...) -> ()): SignalConnection
	assert(not self.Destroyed, DESTROYED_MSG)
	assert(type(fn) == "function", T_ASSERT_FORMAT:format("function", "fn", tostring(fn)))

	local connections = self.Connections
	local index = #connections + 1

	local connection: SignalConnection = {
		Connected = true,
		Disconnect = connection_disconnect,

		[CONNECTION_PRIVATE] = {
			Fn = fn,
			Connections = connections,
			Index = index,
			IsParallel = false
		}
	}
	-- Ensure the thread has a reference to the connection
	local t = task.spawn(mainThreadWorker, connection)

	connection[CONNECTION_PRIVATE].MainThread = t
	table.insert(connections, connection)

	return connection
end
-- Essentially the same as connect, just setting the isParallel flag and asserting if inside an Actor context
local function signal_connectParallel<T...>(self: SimpleSignal<T...>, fn: (T...) -> ()): SignalConnection
	assert(not self.Destroyed, DESTROYED_MSG)
	assert(type(fn) == "function", T_ASSERT_FORMAT:format("function", "fn", tostring(fn)))
	assert(IS_PARALLEL_CONTEXT, "Scripts that connect in parallel must be rooted under an Actor.")

	local c = self:Connect(fn)
	c[CONNECTION_PRIVATE].IsParallel = true

	return c
end

local function signal_once<T...>(self: SimpleSignal<T...>, fn: (T...) -> ()): SignalConnection
	assert(not self.Destroyed, DESTROYED_MSG)
	assert(type(fn) == "function", `Function expected, got {fn}`)

	local connection
	connection = self:Connect(function(...)
		connection:Disconnect()
		fn(...)
	end)

	return connection
end

local function signal_disconnectAll<T...>(self: SimpleSignal<T...>): ()
	assert(not self.Destroyed, DESTROYED_MSG)

	local connections = self.Connections
	local l = #connections

	for _, connection in connections do
		local priv = connection[CONNECTION_PRIVATE]
		connection.Connected = false

		local t = priv.MainThread
		if not t then continue end

		task.cancel(t)
		priv.MainThread = nil
	end
	table.clear(connections)
end

local function signal_fire<T...>(self: SimpleSignal<T...>, ...: T...): ()
	assert(not self.Destroyed, DESTROYED_MSG)

	local connections = self.Connections
	local l = #connections
	if l == 0 then return end

	for i = l, 1, -1 do
		local connection = connections[i]
		local priv = connection[CONNECTION_PRIVATE]

		local t = priv.MainThread
		-- If the main thread is done being used, it can simply be reused (fast path)
		if t ~= nil then
			task.spawn(t, ...)
			continue
		end
		-- If the main thread is still being used (aka yielding),
		-- create a new one and set it as the new main thread (slow path)
		local t = task.spawn(mainThreadWorker, connection, ...)
		priv.MainThread = t
	end
end

local function signal_wait<T...>(self: SimpleSignal<T...>): T...
	assert(not self.Destroyed, DESTROYED_MSG)

	local running = coroutine.running()

	local connection
	connection = self:Connect(function(...)
		connection:Disconnect()
		task.spawn(running, ...)
	end)

	return coroutine.yield()
end

local function signal_destroy<T...>(self: SimpleSignal<T...>): ()
	assert(not self.Destroyed, DESTROYED_MSG)

	self:DisconnectAll()
	self.Destroyed = true
end

--[[
	Create a new SimpleSignal object.
]]
local function signal_new<T...>(): SimpleSignal<T...>
	return {
		Connections = {},
		Destroyed = false,

		Destroy = signal_destroy,
		Connect = signal_connect,
		ConnectParallel = signal_connectParallel,

		Fire = signal_fire,
		Once = signal_once,
		Wait = signal_wait,
		DisconnectAll = signal_disconnectAll
	}
end

return {
	new = signal_new
}


local GreenTea = require(script.GreenTea)
local InstanceClasses = require(script.InstanceClasses)
local tCompat = require(script.tCompat)

export type Cause = GreenTea.Cause
export type Type = GreenTea.Type
export type TuplePacked<T...> = GreenTea.TuplePacked<T...>

local export = {}

export.t = tCompat

export.isGreenTeaType = GreenTea.isGreenTeaType
export.isGtType = GreenTea.isGtType

export.any = GreenTea.any
export.unknown = GreenTea.unknown
export.never = GreenTea.never

export.boolean = GreenTea.boolean
export.bool = GreenTea.bool

export.Instance = GreenTea.Instance
export.isA = InstanceClasses
export.IsA = InstanceClasses

export.coroutine = GreenTea.coroutine
export.thread = GreenTea.thread

export.buffer = GreenTea.buffer

export.userdata = GreenTea.userdata

export.Vector2 = GreenTea.Vector2
export.vector = GreenTea.vector
export.Vector3 = GreenTea.Vector3
export.CFrame = GreenTea.CFrame
export.Color3 = GreenTea.Color3
export.UDim = GreenTea.UDim
export.UDim2 = GreenTea.UDim2
export.Ray = GreenTea.Ray
export.Rect = GreenTea.Rect
export.Region3 = GreenTea.Region3
export.BrickColor = GreenTea.BrickColor
export.Font = GreenTea.Font

export.Enum = GreenTea.Enum
export.EnumItem = GreenTea.EnumItem

export.none = GreenTea.none

export.literal = GreenTea.literal

export.withCustom = GreenTea.withCustom
export.custom = GreenTea.custom

export.number = GreenTea.number

export.string = GreenTea.string

export.isTypeof = GreenTea.isTypeof
export.isType = GreenTea.isType

export.vararg = GreenTea.vararg
export.tuple = GreenTea.tuple

export.args = GreenTea.args
export.returns = GreenTea.returns
export.fn = GreenTea.fn
export.anyfn = GreenTea.anyfn

export.tuplePacked = GreenTea.tuplePacked

export.table = GreenTea.table
export.struct = GreenTea.struct

export.anyTable = GreenTea.anyTable
export.array = GreenTea.array
export.dictionary = GreenTea.dictionary

export.union = GreenTea.union
export.oneOf = GreenTea.oneOf

export.intersection = GreenTea.intersection
export.allOf = GreenTea.allOf

export.optional = GreenTea.optional
export.opt = GreenTea.opt

export.typeof = GreenTea.typeof

export.typecast = GreenTea.typecast
export.asGreenTeaType = GreenTea.asGreenTeaType
export.asGtType = GreenTea.asGtType

export.build = GreenTea.build

export.meta = GreenTea.meta

export.wrapFn = GreenTea.wrapFn
export.wrapFnArgs = GreenTea.wrapFnArgs
export.wrapFnReturns = GreenTea.wrapFnReturns

table.freeze(export)
table.freeze(export.isA)
table.freeze(export.t)
table.freeze(GreenTea.__Cause)
table.freeze(GreenTea.__Type)

for key, value in export do
	GreenTea.__greenteaConstructorsSet[value] = `GreenTea.{key}`
end

for key: string, value: any in pairs(export.isA :: any) do
	GreenTea.__greenteaConstructorsSet[value] = `GreenTea.isA.{key}`
end

for key: string, value: any in pairs(export.t) do
	GreenTea.__greenteaConstructorsSet[value] = `GreenTea.t.{key}`
end

return export