--!optimize 2
--!nolint LocalShadow
local cst = require("./cst")

local function unreachable(_: never): never
	error("unreachable")
end

export type Visitor<State> = {
	visit_token: (state: State, token: cst.Token) -> (),

	visit_generic_declaration: (state: State, node: cst.GenericDeclaration) -> (),
	visit_generic_declaration_end: (state: State, node: cst.GenericDeclaration) -> (),

	visit_generic_declaration_with_defaults: (state: State, node: cst.GenericDeclarationWithDefaults) -> (),
	visit_generic_declaration_with_defaults_end: (state: State, node: cst.GenericDeclarationWithDefaults) -> (),

	visit_type_arg: (state: State, node: cst.TypeArg) -> (),
	visit_type_arg_end: (state: State, node: cst.TypeArg) -> (),

	visit_table_type_field_name_prop: (state: State, node: cst.TableTypeField_NameProp) -> (),
	visit_table_type_field_name_prop_end: (state: State, node: cst.TableTypeField_NameProp) -> (),
	visit_table_type_field_string_prop: (state: State, node: cst.TableTypeField_StringProp) -> (),
	visit_table_type_field_string_prop_end: (state: State, node: cst.TableTypeField_StringProp) -> (),
	visit_table_type_field_indexer: (state: State, node: cst.TableTypeField_Indexer) -> (),
	visit_table_type_field_indexer_end: (state: State, node: cst.TableTypeField_Indexer) -> (),
	visit_table_type_field: (state: State, node: cst.TableTypeField) -> (),
	visit_table_type_field_end: (state: State, node: cst.TableTypeField) -> (),

	visit_type_nil: (state: State, node: cst.Type_Nil) -> (),
	visit_type_nil_end: (state: State, node: cst.Type_Nil) -> (),
	visit_type_boolean: (state: State, node: cst.Type_Boolean) -> (),
	visit_type_boolean_end: (state: State, node: cst.Type_Boolean) -> (),
	visit_type_string: (state: State, node: cst.Type_String) -> (),
	visit_type_string_end: (state: State, node: cst.Type_String) -> (),
	visit_type_reference: (state: State, node: cst.Type_Reference) -> (),
	visit_type_reference_end: (state: State, node: cst.Type_Reference) -> (),
	visit_type_typeof: (state: State, node: cst.Type_Typeof) -> (),
	visit_type_typeof_end: (state: State, node: cst.Type_Typeof) -> (),
	visit_type_array: (state: State, node: cst.Type_Array) -> (),
	visit_type_array_end: (state: State, node: cst.Type_Array) -> (),
	visit_type_table: (state: State, node: cst.Type_Table) -> (),
	visit_type_table_end: (state: State, node: cst.Type_Table) -> (),
	visit_type_function: (state: State, node: cst.Type_Function) -> (),
	visit_type_function_end: (state: State, node: cst.Type_Function) -> (),
	visit_type_paren: (state: State, node: cst.Type_Paren) -> (),
	visit_type_paren_end: (state: State, node: cst.Type_Paren) -> (),
	visit_type_optional: (state: State, node: cst.Type_Optional) -> (),
	visit_type_optional_end: (state: State, node: cst.Type_Optional) -> (),
	visit_type_union: (state: State, node: cst.Type_Union) -> (),
	visit_type_union_end: (state: State, node: cst.Type_Union) -> (),
	visit_type_intersection: (state: State, node: cst.Type_Intersection) -> (),
	visit_type_intersection_end: (state: State, node: cst.Type_Intersection) -> (),
	visit_type: (state: State, node: cst.Type) -> (),
	visit_type_end: (state: State, node: cst.Type) -> (),

	visit_variadic_type_pack: (state: State, node: cst.VariadicTypePack) -> (),
	visit_variadic_type_pack_end: (state: State, node: cst.VariadicTypePack) -> (),

	visit_generic_type_pack: (state: State, node: cst.GenericTypePack) -> (),
	visit_generic_type_pack_end: (state: State, node: cst.GenericTypePack) -> (),

	visit_type_list: (state: State, node: cst.TypeList) -> (),
	visit_type_list_end: (state: State, node: cst.TypeList) -> (),

	visit_tail_type: (state: State, node: cst.TailType) -> (),
	visit_tail_type_end: (state: State, node: cst.TailType) -> (),

	visit_type_pack: (state: State, node: cst.TypePack) -> (),
	visit_type_pack_end: (state: State, node: cst.TypePack) -> (),

	visit_return_type: (state: State, node: cst.ReturnType) -> (),
	visit_return_type_end: (state: State, node: cst.ReturnType) -> (),

	visit_binding: (state: State, node: cst.Binding) -> (),
	visit_binding_end: (state: State, node: cst.Binding) -> (),

	visit_istring_simple: (state: State, node: cst.IString_Simple) -> (),
	visit_istring_simple_end: (state: State, node: cst.IString_Simple) -> (),
	visit_istring_complex: (state: State, node: cst.IString_Complex) -> (),
	visit_istring_complex_end: (state: State, node: cst.IString_Complex) -> (),
	visit_istring: (state: State, node: cst.IString) -> (),
	visit_istring_end: (state: State, node: cst.IString) -> (),

	visit_if_else_expr_branch: (state: State, node: cst.IfElseExprBranch) -> (),
	visit_if_else_expr_branch_end: (state: State, node: cst.IfElseExprBranch) -> (),

	visit_expr_nil: (state: State, node: cst.Expr_Nil) -> (),
	visit_expr_nil_end: (state: State, node: cst.Expr_Nil) -> (),
	visit_expr_boolean: (state: State, node: cst.Expr_Boolean) -> (),
	visit_expr_boolean_end: (state: State, node: cst.Expr_Boolean) -> (),
	visit_expr_number: (state: State, node: cst.Expr_Number) -> (),
	visit_expr_number_end: (state: State, node: cst.Expr_Number) -> (),
	visit_expr_string: (state: State, node: cst.Expr_String) -> (),
	visit_expr_string_end: (state: State, node: cst.Expr_String) -> (),
	visit_expr_varargs: (state: State, node: cst.Expr_Varargs) -> (),
	visit_expr_varargs_end: (state: State, node: cst.Expr_Varargs) -> (),
	visit_expr_istring: (state: State, node: cst.Expr_IString) -> (),
	visit_expr_istring_end: (state: State, node: cst.Expr_IString) -> (),
	visit_expr_table: (state: State, node: cst.Expr_Table) -> (),
	visit_expr_table_end: (state: State, node: cst.Expr_Table) -> (),
	visit_expr_function: (state: State, node: cst.Expr_Function) -> (),
	visit_expr_function_end: (state: State, node: cst.Expr_Function) -> (),
	visit_expr_if_else: (state: State, node: cst.Expr_IfElse) -> (),
	visit_expr_if_else_end: (state: State, node: cst.Expr_IfElse) -> (),
	visit_expr_var: (state: State, node: cst.Expr_Var) -> (),
	visit_expr_var_end: (state: State, node: cst.Expr_Var) -> (),
	visit_expr_assertion: (state: State, node: cst.Expr_Assertion) -> (),
	visit_expr_assertion_end: (state: State, node: cst.Expr_Assertion) -> (),
	visit_expr_unary: (state: State, node: cst.Expr_Unary) -> (),
	visit_expr_unary_end: (state: State, node: cst.Expr_Unary) -> (),
	visit_expr_binary: (state: State, node: cst.Expr_Binary) -> (),
	visit_expr_binary_end: (state: State, node: cst.Expr_Binary) -> (),
	visit_expr: (state: State, node: cst.Expr) -> (),
	visit_expr_end: (state: State, node: cst.Expr) -> (),

	visit_function_arg_pack: (state: State, node: cst.FunctionArg_Pack) -> (),
	visit_function_arg_pack_end: (state: State, node: cst.FunctionArg_Pack) -> (),
	visit_function_arg_table: (state: State, node: cst.FunctionArg_Table) -> (),
	visit_function_arg_table_end: (state: State, node: cst.FunctionArg_Table) -> (),
	visit_function_arg_string: (state: State, node: cst.FunctionArg_String) -> (),
	visit_function_arg_string_end: (state: State, node: cst.FunctionArg_String) -> (),
	visit_function_arg: (state: State, node: cst.FunctionArg) -> (),
	visit_function_arg_end: (state: State, node: cst.FunctionArg) -> (),

	visit_var_root_name: (state: State, node: cst.VarRoot_Name) -> (),
	visit_var_root_name_end: (state: State, node: cst.VarRoot_Name) -> (),
	visit_var_root_paren: (state: State, node: cst.VarRoot_Paren) -> (),
	visit_var_root_paren_end: (state: State, node: cst.VarRoot_Paren) -> (),
	visit_var_root: (state: State, node: cst.VarRoot) -> (),
	visit_var_root_end: (state: State, node: cst.VarRoot) -> (),

	visit_var_suffix_name_index: (state: State, node: cst.VarSuffix_NameIndex) -> (),
	visit_var_suffix_name_index_end: (state: State, node: cst.VarSuffix_NameIndex) -> (),
	visit_var_suffix_expr_index: (state: State, node: cst.VarSuffix_ExprIndex) -> (),
	visit_var_suffix_expr_index_end: (state: State, node: cst.VarSuffix_ExprIndex) -> (),
	visit_var_suffix_call: (state: State, node: cst.VarSuffix_Call) -> (),
	visit_var_suffix_call_end: (state: State, node: cst.VarSuffix_Call) -> (),
	visit_var_suffix: (state: State, node: cst.VarSuffix) -> (),
	visit_var_suffix_end: (state: State, node: cst.VarSuffix) -> (),

	visit_var: (state: State, node: cst.Var) -> (),
	visit_var_end: (state: State, node: cst.Var) -> (),

	visit_table_field_name_key: (state: State, node: cst.TableField_NameKey) -> (),
	visit_table_field_name_key_end: (state: State, node: cst.TableField_NameKey) -> (),
	visit_table_field_expr_key: (state: State, node: cst.TableField_ExprKey) -> (),
	visit_table_field_expr_key_end: (state: State, node: cst.TableField_ExprKey) -> (),
	visit_table_field_no_key: (state: State, node: cst.TableField_NoKey) -> (),
	visit_table_field_no_key_end: (state: State, node: cst.TableField_NoKey) -> (),
	visit_table_field: (state: State, node: cst.TableField) -> (),
	visit_table_field_end: (state: State, node: cst.TableField) -> (),

	visit_table: (state: State, node: cst.Table) -> (),
	visit_table_end: (state: State, node: cst.Table) -> (),

	visit_function_body: (state: State, node: cst.FunctionBody) -> (),
	visit_function_body_end: (state: State, node: cst.FunctionBody) -> (),

	visit_if_stat_branch: (state: State, node: cst.IfStatBranch) -> (),
	visit_if_stat_branch_end: (state: State, node: cst.IfStatBranch) -> (),

	visit_stat_assign: (state: State, node: cst.Stat_Assign) -> (),
	visit_stat_assign_end: (state: State, node: cst.Stat_Assign) -> (),
	visit_stat_compound_assign: (state: State, node: cst.Stat_CompoundAssign) -> (),
	visit_stat_compound_assign_end: (state: State, node: cst.Stat_CompoundAssign) -> (),
	visit_stat_call: (state: State, node: cst.Stat_Call) -> (),
	visit_stat_call_end: (state: State, node: cst.Stat_Call) -> (),
	visit_stat_do: (state: State, node: cst.Stat_Do) -> (),
	visit_stat_do_end: (state: State, node: cst.Stat_Do) -> (),
	visit_stat_while: (state: State, node: cst.Stat_While) -> (),
	visit_stat_while_end: (state: State, node: cst.Stat_While) -> (),
	visit_stat_repeat: (state: State, node: cst.Stat_Repeat) -> (),
	visit_stat_repeat_end: (state: State, node: cst.Stat_Repeat) -> (),
	visit_stat_if: (state: State, node: cst.Stat_If) -> (),
	visit_stat_if_end: (state: State, node: cst.Stat_If) -> (),
	visit_stat_numeric_for: (state: State, node: cst.Stat_NumericFor) -> (),
	visit_stat_numeric_for_end: (state: State, node: cst.Stat_NumericFor) -> (),
	visit_stat_for_in: (state: State, node: cst.Stat_ForIn) -> (),
	visit_stat_for_in_end: (state: State, node: cst.Stat_ForIn) -> (),
	visit_stat_function: (state: State, node: cst.Stat_Function) -> (),
	visit_stat_function_end: (state: State, node: cst.Stat_Function) -> (),
	visit_stat_local_function: (state: State, node: cst.Stat_LocalFunction) -> (),
	visit_stat_local_function_end: (state: State, node: cst.Stat_LocalFunction) -> (),
	visit_stat_type_function: (state: State, node: cst.Stat_TypeFunction) -> (),
	visit_stat_type_function_end: (state: State, node: cst.Stat_TypeFunction) -> (),
	visit_stat_local_variable: (state: State, node: cst.Stat_LocalVariable) -> (),
	visit_stat_local_variable_end: (state: State, node: cst.Stat_LocalVariable) -> (),
	visit_stat_type: (state: State, node: cst.Stat_Type) -> (),
	visit_stat_type_end: (state: State, node: cst.Stat_Type) -> (),
	visit_stat: (state: State, node: cst.Stat) -> (),
	visit_stat_end: (state: State, node: cst.Stat) -> (),

	visit_last_stat_return: (state: State, node: cst.LastStat_Return) -> (),
	visit_last_stat_return_end: (state: State, node: cst.LastStat_Return) -> (),
	visit_last_stat_break: (state: State, node: cst.LastStat_Break) -> (),
	visit_last_stat_break_end: (state: State, node: cst.LastStat_Break) -> (),
	visit_last_stat_continue: (state: State, node: cst.LastStat) -> (),
	visit_last_stat_continue_end: (state: State, node: cst.LastStat) -> (),
	visit_last_stat: (state: State, node: cst.LastStat) -> (),
	visit_last_stat_end: (state: State, node: cst.LastStat) -> (),

	visit_block: (state: State, node: cst.Block) -> (),
	visit_block_end: (state: State, node: cst.Block) -> (),

	visit_cst: (state: State, node: cst.Cst) -> (),
	visit_cst_end: (state: State, node: cst.Cst) -> (),
}

local function visitor<State>(): Visitor<State>
	local noop: any = function() end

	return {
		visit_token = noop,

		visit_generic_declaration = noop,
		visit_generic_declaration_end = noop,

		visit_generic_declaration_with_defaults = noop,
		visit_generic_declaration_with_defaults_end = noop,

		visit_type_arg = noop,
		visit_type_arg_end = noop,

		visit_table_type_field_name_prop = noop,
		visit_table_type_field_name_prop_end = noop,
		visit_table_type_field_string_prop = noop,
		visit_table_type_field_string_prop_end = noop,
		visit_table_type_field_indexer = noop,
		visit_table_type_field_indexer_end = noop,
		visit_table_type_field = noop,
		visit_table_type_field_end = noop,

		visit_type_nil = noop,
		visit_type_nil_end = noop,
		visit_type_boolean = noop,
		visit_type_boolean_end = noop,
		visit_type_string = noop,
		visit_type_string_end = noop,
		visit_type_reference = noop,
		visit_type_reference_end = noop,
		visit_type_typeof = noop,
		visit_type_typeof_end = noop,
		visit_type_array = noop,
		visit_type_array_end = noop,
		visit_type_table = noop,
		visit_type_table_end = noop,
		visit_type_function = noop,
		visit_type_function_end = noop,
		visit_type_paren = noop,
		visit_type_paren_end = noop,
		visit_type_optional = noop,
		visit_type_optional_end = noop,
		visit_type_union = noop,
		visit_type_union_end = noop,
		visit_type_intersection = noop,
		visit_type_intersection_end = noop,
		visit_type = noop,
		visit_type_end = noop,

		visit_variadic_type_pack = noop,
		visit_variadic_type_pack_end = noop,

		visit_generic_type_pack = noop,
		visit_generic_type_pack_end = noop,

		visit_type_list = noop,
		visit_type_list_end = noop,

		visit_tail_type = noop,
		visit_tail_type_end = noop,

		visit_type_pack = noop,
		visit_type_pack_end = noop,

		visit_return_type = noop,
		visit_return_type_end = noop,

		visit_binding = noop,
		visit_binding_end = noop,

		visit_istring_simple = noop,
		visit_istring_simple_end = noop,

		visit_istring_complex = noop,
		visit_istring_complex_end = noop,

		visit_istring = noop,
		visit_istring_end = noop,

		visit_if_else_expr_branch = noop,
		visit_if_else_expr_branch_end = noop,

		visit_expr_nil = noop,
		visit_expr_nil_end = noop,
		visit_expr_boolean = noop,
		visit_expr_boolean_end = noop,
		visit_expr_number = noop,
		visit_expr_number_end = noop,
		visit_expr_string = noop,
		visit_expr_string_end = noop,
		visit_expr_varargs = noop,
		visit_expr_varargs_end = noop,
		visit_expr_istring = noop,
		visit_expr_istring_end = noop,
		visit_expr_table = noop,
		visit_expr_table_end = noop,
		visit_expr_function = noop,
		visit_expr_function_end = noop,
		visit_expr_if_else = noop,
		visit_expr_if_else_end = noop,
		visit_expr_var = noop,
		visit_expr_var_end = noop,
		visit_expr_assertion = noop,
		visit_expr_assertion_end = noop,
		visit_expr_unary = noop,
		visit_expr_unary_end = noop,
		visit_expr_binary = noop,
		visit_expr_binary_end = noop,
		visit_expr = noop,
		visit_expr_end = noop,

		visit_function_arg_pack = noop,
		visit_function_arg_pack_end = noop,
		visit_function_arg_table = noop,
		visit_function_arg_table_end = noop,
		visit_function_arg_string = noop,
		visit_function_arg_string_end = noop,
		visit_function_arg = noop,
		visit_function_arg_end = noop,

		visit_var_root_name = noop,
		visit_var_root_name_end = noop,
		visit_var_root_paren = noop,
		visit_var_root_paren_end = noop,
		visit_var_root = noop,
		visit_var_root_end = noop,

		visit_var_suffix_name_index = noop,
		visit_var_suffix_name_index_end = noop,
		visit_var_suffix_expr_index = noop,
		visit_var_suffix_expr_index_end = noop,
		visit_var_suffix_call = noop,
		visit_var_suffix_call_end = noop,
		visit_var_suffix = noop,
		visit_var_suffix_end = noop,

		visit_var = noop,
		visit_var_end = noop,

		visit_table_field_name_key = noop,
		visit_table_field_name_key_end = noop,
		visit_table_field_expr_key = noop,
		visit_table_field_expr_key_end = noop,
		visit_table_field_no_key = noop,
		visit_table_field_no_key_end = noop,
		visit_table_field = noop,
		visit_table_field_end = noop,

		visit_table = noop,
		visit_table_end = noop,

		visit_function_body = noop,
		visit_function_body_end = noop,

		visit_if_stat_branch = noop,
		visit_if_stat_branch_end = noop,

		visit_stat_assign = noop,
		visit_stat_assign_end = noop,
		visit_stat_compound_assign = noop,
		visit_stat_compound_assign_end = noop,
		visit_stat_call = noop,
		visit_stat_call_end = noop,
		visit_stat_do = noop,
		visit_stat_do_end = noop,
		visit_stat_while = noop,
		visit_stat_while_end = noop,
		visit_stat_repeat = noop,
		visit_stat_repeat_end = noop,
		visit_stat_if = noop,
		visit_stat_if_end = noop,
		visit_stat_numeric_for = noop,
		visit_stat_numeric_for_end = noop,
		visit_stat_for_in = noop,
		visit_stat_for_in_end = noop,
		visit_stat_function = noop,
		visit_stat_function_end = noop,
		visit_stat_local_function = noop,
		visit_stat_local_function_end = noop,
		visit_stat_type_function = noop,
		visit_stat_type_function_end = noop,
		visit_stat_local_variable = noop,
		visit_stat_local_variable_end = noop,
		visit_stat_type = noop,
		visit_stat_type_end = noop,
		visit_stat = noop,
		visit_stat_end = noop,

		visit_last_stat_return = noop,
		visit_last_stat_return_end = noop,
		visit_last_stat_break = noop,
		visit_last_stat_break_end = noop,
		visit_last_stat_continue = noop,
		visit_last_stat_continue_end = noop,
		visit_last_stat = noop,
		visit_last_stat_end = noop,

		visit_block = noop,
		visit_block_end = noop,

		visit_cst = noop,
		visit_cst_end = noop,
	}
end

local visit_generic_declaration: <State>(visitor: Visitor<State>, state: State, node: cst.GenericDeclaration) -> ()
local visit_generic_declaration_with_defaults: <State>(visitor: Visitor<State>, state: State, node: cst.GenericDeclarationWithDefaults) -> ()
local visit_type: <State>(visitor: Visitor<State>, state: State, node: cst.Type) -> ()
local visit_tail_type: <State>(visitor: Visitor<State>, state: State, node: cst.TailType) -> ()
local visit_type_pack: <State>(visitor: Visitor<State>, state: State, node: cst.TypePack) -> ()
local visit_return_type: <State>(visitor: Visitor<State>, state: State, node: cst.ReturnType) -> ()
local visit_expr: <State>(visitor: Visitor<State>, state: State, node: cst.Expr) -> ()
local visit_var: <State>(visitor: Visitor<State>, state: State, node: cst.Var) -> ()
local visit_table: <State>(visitor: Visitor<State>, state: State, node: cst.Table) -> ()
local visit_function_body: <State>(visitor: Visitor<State>, state: State, node: cst.FunctionBody) -> ()
local visit_stat: <State>(visitor: Visitor<State>, state: State, node: cst.Stat) -> ()
local visit_last_stat: <State>(visitor: Visitor<State>, state: State, node: cst.LastStat) -> ()
local visit_block: <State>(visitor: Visitor<State>, state: State, node: cst.Block) -> ()

local function visit_token<State>(visitor: Visitor<State>, state: State, token: cst.Token)
	visitor.visit_token(state, token)
end

function visit_generic_declaration<State>(visitor: Visitor<State>, state: State, node: cst.GenericDeclaration)
	visitor.visit_generic_declaration(state, node)
	visit_token(visitor, state, node.angles.open)

	for _, pair in node.names do
		local node, sep = pair.node, pair.sep

		visit_token(visitor, state, node)

		if sep then
			visit_token(visitor, state, sep)
		end
	end

	for _, pair in node.packs do
		local node, sep = pair.node, pair.sep

		visit_token(visitor, state, node.name)
		visit_token(visitor, state, node.dots)

		if sep then
			visit_token(visitor, state, sep)
		end
	end

	visit_token(visitor, state, node.angles.close)
	visitor.visit_generic_declaration_end(state, node)
end

function visit_generic_declaration_with_defaults<State>(visitor: Visitor<State>, state: State, node: cst.GenericDeclarationWithDefaults)
	visitor.visit_generic_declaration_with_defaults(state, node)
	visit_token(visitor, state, node.angles.open)

	for _, pair in node.names do
		local node, sep = pair.node, pair.sep

		visit_token(visitor, state, node.name)

		local default = node.default
		if default then
			visit_token(visitor, state, default.equals)
			visit_type(visitor, state, default.type)
		end

		if sep then
			visit_token(visitor, state, sep)
		end
	end

	for _, pair in node.packs do
		local node, sep = pair.node, pair.sep

		visit_token(visitor, state, node.name)
		visit_token(visitor, state, node.dots)

		local default = node.default
		if default then
			visit_token(visitor, state, default.equals)
			visit_type_pack(visitor, state, default.pack)
		end

		if sep then
			visit_token(visitor, state, sep)
		end
	end

	visit_token(visitor, state, node.angles.close)
	visitor.visit_generic_declaration_with_defaults_end(state, node)
end

function visit_type<State>(visitor: Visitor<State>, state: State, node: cst.Type)
	visitor.visit_type(state, node)

	if node.kind == "nil" then
		visitor.visit_type_nil(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_type_nil_end(state, node)
	elseif node.kind == "boolean" then
		visitor.visit_type_boolean(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_type_boolean_end(state, node)
	elseif node.kind == "string" then
		visitor.visit_type_string(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_type_string_end(state, node)
	elseif node.kind == "reference" then
		node = node :: cst.Type_Reference
		visitor.visit_type_reference(state, node)

		local prefix = node.prefix
		if prefix then
			visit_token(visitor, state, prefix.name)
			visit_token(visitor, state, prefix.dot)
		end

		visit_token(visitor, state, node.name)

		local generics = node.generics
		if generics then
			visit_token(visitor, state, generics.angles.open)

			for _, pair in generics.types do
				local node, sep = pair.node, pair.sep

				visitor.visit_type_arg(state, node)

				if
					node.kind == "variadic"
					or node.kind == "generic"
					or node.kind == "list"
				then
					visit_type_pack(visitor, state, node)
				else
					visit_type(visitor, state, node)
				end

				visitor.visit_type_arg_end(state, node)

				if sep then
					visit_token(visitor, state, sep)
				end
			end

			visit_token(visitor, state, generics.angles.close)
		end

		visitor.visit_type_reference_end(state, node)
	elseif node.kind == "typeof" then
		visitor.visit_type_typeof(state, node)
		visit_token(visitor, state, node.typeof_tok)
		visit_token(visitor, state, node.parens.open)
		visit_expr(visitor, state, node.expr)
		visit_token(visitor, state, node.parens.close)
		visitor.visit_type_typeof_end(state, node)
	elseif node.kind == "array" then
		visitor.visit_type_array(state, node)
		visit_token(visitor, state, node.braces.open)
		visit_type(visitor, state, node.type)
		visit_token(visitor, state, node.braces.close)
		visitor.visit_type_array_end(state, node)
	elseif node.kind == "table" then
		visitor.visit_type_table(state, node)
		visit_token(visitor, state, node.braces.open)

		for _, pair in node.fields do
			local node, sep = pair.node, pair.sep

			visitor.visit_table_type_field(state, node)

			if node.kind == "nameprop" then
				visitor.visit_table_type_field_name_prop(state, node)

				local access = node.access
				if access then
					visit_token(visitor, state, access)
				end

				visit_token(visitor, state, node.name)
				visit_token(visitor, state, node.colon)
				visit_type(visitor, state, node.type)
				visitor.visit_table_type_field_name_prop_end(state, node)
			elseif node.kind == "stringprop" then
				visitor.visit_table_type_field_string_prop(state, node)

				local access = node.access
				if access then
					visit_token(visitor, state, access)
				end

				visit_token(visitor, state, node.bracks.open)
				visit_token(visitor, state, node.key)
				visit_token(visitor, state, node.bracks.close)
				visit_token(visitor, state, node.colon)
				visit_type(visitor, state, node.type)
				visitor.visit_table_type_field_string_prop_end(state, node)
			elseif node.kind == "indexer" then
				visitor.visit_table_type_field_indexer(state, node)

				local access = node.access
				if access then
					visit_token(visitor, state, access)
				end

				visit_token(visitor, state, node.bracks.open)
				visit_type(visitor, state, node.key)
				visit_token(visitor, state, node.bracks.close)
				visit_token(visitor, state, node.colon)
				visit_type(visitor, state, node.type)
				visitor.visit_table_type_field_indexer_end(state, node)
			else
				unreachable(node.kind)
			end

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visit_token(visitor, state, node.braces.close)
		visitor.visit_type_table_end(state, node)
	elseif node.kind == "function" then
		node = (node :: any) :: cst.Type_Function
		visitor.visit_type_function(state, node)

		local generics = node.generics
		if generics then
			visit_generic_declaration(visitor, state, generics)
		end

		visit_token(visitor, state, node.parens.open)

		for _, pair in node.params do
			local node, sep = pair.node, pair.sep

			local name = node.name
			if name then
				visit_token(visitor, state, name.name)
				visit_token(visitor, state, name.colon)
			end

			visit_type(visitor, state, node.type)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		local varargs = node.varargs
		if varargs then
			visit_tail_type(visitor, state, varargs)
		end

		visit_token(visitor, state, node.parens.close)
		visit_token(visitor, state, node.arrow)
		visit_return_type(visitor, state, node.ret)

		visitor.visit_type_function_end(state, node)
	elseif node.kind == "paren" then
		node = node :: cst.Type_Paren
		visitor.visit_type_paren(state, node)
		visit_token(visitor, state, node.parens.open)
		visit_type(visitor, state, node.type)
		visit_token(visitor, state, node.parens.close)
		visitor.visit_type_paren_end(state, node)
	elseif node.kind == "optional" then
		visitor.visit_type_optional(state, node)
		visit_type(visitor, state, node.type)
		visit_token(visitor, state, node.question)
		visitor.visit_type_optional_end(state, node)
	elseif node.kind == "union" then
		node = node :: cst.Type_Union
		visitor.visit_type_union(state, node)

		local leading = node.leading
		if leading then
			visit_token(visitor, state, leading)
		end

		for _, pair in node.types do
			local node, sep = pair.node, pair.sep

			visit_type(visitor, state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visitor.visit_type_union_end(state, node)
	elseif node.kind == "intersection" then
		node = node :: cst.Type_Intersection
		visitor.visit_type_intersection(state, node)

		local leading = node.leading
		if leading then
			visit_token(visitor, state, leading)
		end

		for _, pair in node.types do
			local node, sep = pair.node, pair.sep

			visit_type(visitor, state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visitor.visit_type_intersection_end(state, node)
	else
		unreachable(node.kind)
	end

	visitor.visit_type_end(state, node)
end

do
	local function visit_any_pack<State>(visitor: Visitor<State>, state: State, node: cst.TypePack)
		if node.kind == "variadic" then
			visitor.visit_variadic_type_pack(state, node)
			visit_token(visitor, state, node.dots)
			visit_type(visitor, state, node.type)
			visitor.visit_variadic_type_pack_end(state, node)
		elseif node.kind == "generic" then
			visitor.visit_generic_type_pack(state, node)
			visit_token(visitor, state, node.name)
			visit_token(visitor, state, node.dots)
			visitor.visit_generic_type_pack_end(state, node)
		elseif node.kind == "list" then
			visitor.visit_type_list(state, node)
			visit_token(visitor, state, node.parens.open)

			for _, pair in node.types do
				local node, sep = pair.node, pair.sep

				visit_type(visitor, state, node)

				if sep then
					visit_token(visitor, state, sep)
				end
			end

			local tail = node.tail
			if tail then
				visit_tail_type(visitor, state, tail)
			end

			visit_token(visitor, state, node.parens.close)
			visitor.visit_type_list_end(state, node)
		else
			unreachable(node.kind)
		end
	end

	function visit_tail_type<State>(visitor: Visitor<State>, state: State, node: cst.TailType)
		visitor.visit_tail_type(state, node)
		visit_any_pack(visitor, state, node)
		visitor.visit_tail_type_end(state, node)
	end

	function visit_type_pack<State>(visitor: Visitor<State>, state: State, node: cst.TypePack)
		visitor.visit_type_pack(state, node)
		visit_any_pack(visitor, state, node)
		visitor.visit_type_pack_end(state, node)
	end

	function visit_return_type<State>(visitor: Visitor<State>, state: State, node: cst.ReturnType)
		visitor.visit_return_type(state, node)

		if
			node.kind == "variadic"
			or node.kind == "generic"
			or node.kind == "list"
		then
			visit_type_pack(visitor, state, node)
		else
			visit_type(visitor, state, node)
		end

		visitor.visit_return_type_end(state, node)
	end
end

function visit_expr<State>(visitor: Visitor<State>, state: State, node: cst.Expr)
	visitor.visit_expr(state, node)

	if node.kind == "nil" then
		visitor.visit_expr_nil(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_expr_nil_end(state, node)
	elseif node.kind == "boolean" then
		visitor.visit_expr_boolean(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_expr_boolean_end(state, node)
	elseif node.kind == "number" then
		visitor.visit_expr_number(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_expr_number_end(state, node)
	elseif node.kind == "string" then
		visitor.visit_expr_string(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_expr_string_end(state, node)
	elseif node.kind == "varargs" then
		visitor.visit_expr_varargs(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_expr_varargs_end(state, node)
	elseif node.kind == "istring" then
		visitor.visit_expr_istring(state, node)

		local istring = node.istring
		visitor.visit_istring(state, istring)

		if istring.kind == "simple" then
			visitor.visit_istring_simple(state, istring)
			visit_token(visitor, state, istring.tok)
			visitor.visit_istring_simple_end(state, istring)
		elseif istring.kind == "complex" then
			visitor.visit_istring_complex(state, istring)
			visit_token(visitor, state, istring.first)

			for _, pair in istring.middle do
				local node, sep = pair.node, pair.sep

				visit_expr(visitor, state, node)

				if sep then
					visit_token(visitor, state, sep)
				end
			end

			visit_token(visitor, state, istring.last)
			visitor.visit_istring_complex_end(state, istring)
		else
			unreachable(istring.kind)
		end

		visitor.visit_istring_end(state, istring)
		visitor.visit_expr_istring_end(state, node)
	elseif node.kind == "table" then
		visitor.visit_expr_table(state, node)
		visit_table(visitor, state, node.table)
		visitor.visit_expr_table_end(state, node)
	elseif node.kind == "function" then
		visitor.visit_expr_function(state, node)

		for _, attribute in node.attributes do
			visit_token(visitor, state, attribute)
		end

		visit_token(visitor, state, node.function_tok)
		visit_function_body(visitor, state, node.body)
		visitor.visit_expr_function_end(state, node)
	elseif node.kind == "ifelse" then
		node = node :: cst.Expr_IfElse
		visitor.visit_expr_if_else(state, node)
		visit_token(visitor, state, node.if_tok)

		for _, pair in node.branches do
			local node, sep = pair.node, pair.sep

			visitor.visit_if_else_expr_branch(state, node)

			visit_expr(visitor, state, node.condition)
			visit_token(visitor, state, node.then_tok)
			visit_expr(visitor, state, node.body)

			visitor.visit_if_else_expr_branch_end(state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visit_token(visitor, state, node.else_tok)
		visit_expr(visitor, state, node.else_body)
		visitor.visit_expr_if_else_end(state, node)
	elseif node.kind == "var" then
		visitor.visit_expr_var(state, node)
		visit_var(visitor, state, node.var)
		visitor.visit_expr_var_end(state, node)
	elseif node.kind == "assertion" then
		node = node :: cst.Expr_Assertion
		visitor.visit_expr_assertion(state, node)
		visit_expr(visitor, state, node.expr)
		visit_token(visitor, state, node.colon)
		visit_type(visitor, state, node.type)
		visitor.visit_expr_assertion_end(state, node)
	elseif node.kind == "unary" then
		visitor.visit_expr_unary(state, node)
		visit_token(visitor, state, node.operator)
		visit_expr(visitor, state, node.expr)
		visitor.visit_expr_unary_end(state, node)
	elseif node.kind == "binary" then
		node = node :: cst.Expr_Binary
		visitor.visit_expr_binary(state, node)
		visit_expr(visitor, state, node.left)
		visit_token(visitor, state, node.operator)
		visit_expr(visitor, state, node.right)
		visitor.visit_expr_binary_end(state, node)
	else
		unreachable(node.kind)
	end

	visitor.visit_expr_end(state, node)
end

function visit_var<State>(visitor: Visitor<State>, state: State, node: cst.Var)
	visitor.visit_var(state, node)

	local root = node.root
	visitor.visit_var_root(state, root)

	if root.kind == "name" then
		visitor.visit_var_root_name(state, root)
		visit_token(visitor, state, root.tok)
		visitor.visit_var_root_name_end(state, root)
	elseif root.kind == "paren" then
		visitor.visit_var_root_paren(state, root)
		visit_token(visitor, state, root.parens.open)
		visit_expr(visitor, state, root.expr)
		visit_token(visitor, state, root.parens.close)
		visitor.visit_var_root_paren_end(state, root)
	else
		unreachable(root.kind)
	end

	visitor.visit_var_root_end(state, root)

	for _, suffix in node.suffixes do
		visitor.visit_var_suffix(state, suffix)

		if suffix.kind == "nameindex" then
			visitor.visit_var_suffix_name_index(state, suffix)
			visit_token(visitor, state, suffix.dot)
			visit_token(visitor, state, suffix.name)
			visitor.visit_var_suffix_name_index_end(state, suffix)
		elseif suffix.kind == "exprindex" then
			visitor.visit_var_suffix_expr_index(state, suffix)
			visit_token(visitor, state, suffix.bracks.open)
			visit_expr(visitor, state, suffix.expr)
			visit_token(visitor, state, suffix.bracks.close)
			visitor.visit_var_suffix_expr_index_end(state, suffix)
		elseif suffix.kind == "call" then
			visitor.visit_var_suffix_call(state, suffix)

			local method = suffix.method
			if method then
				visit_token(visitor, state, method.colon)
				visit_token(visitor, state, method.name)
			end

			local arg = suffix.arg
			visitor.visit_function_arg(state, arg)

			if arg.kind == "pack" then
				visitor.visit_function_arg_pack(state, arg)
				visit_token(visitor, state, arg.parens.open)

				for _, pair in arg.exprs do
					local node, sep = pair.node, pair.sep

					visit_expr(visitor, state, node)

					if sep then
						visit_token(visitor, state, sep)
					end
				end

				visit_token(visitor, state, arg.parens.close)
				visitor.visit_function_arg_pack_end(state, arg)
			elseif arg.kind == "table" then
				visitor.visit_function_arg_table(state, arg)
				visit_table(visitor, state, arg.table)
				visitor.visit_function_arg_table_end(state, arg)
			elseif arg.kind == "string" then
				visitor.visit_function_arg_string(state, arg)
				visit_token(visitor, state, arg.tok)
				visitor.visit_function_arg_string_end(state, arg)
			else
				unreachable(arg.kind)
			end
		else
			unreachable(suffix.kind)
		end

		visitor.visit_var_suffix_end(state, suffix)
	end

	visitor.visit_var_end(state, node)
end

function visit_table<State>(visitor: Visitor<State>, state: State, node: cst.Table)
	visitor.visit_table(state, node)
	visit_token(visitor, state, node.braces.open)

	for _, pair in node.fields do
		local node, sep = pair.node, pair.sep

		visitor.visit_table_field(state, node)

		if node.kind == "namekey" then
			visitor.visit_table_field_name_key(state, node)
			visit_token(visitor, state, node.name)
			visit_token(visitor, state, node.equals)
			visit_expr(visitor, state, node.value)
			visitor.visit_table_field_name_key_end(state, node)
		elseif node.kind == "exprkey" then
			visitor.visit_table_field_expr_key(state, node)
			visit_token(visitor, state, node.bracks.open)
			visit_expr(visitor, state, node.key)
			visit_token(visitor, state, node.bracks.close)
			visit_token(visitor, state, node.equals)
			visit_expr(visitor, state, node.value)
			visitor.visit_table_field_expr_key_end(state, node)
		elseif node.kind == "nokey" then
			visitor.visit_table_field_no_key(state, node)
			visit_expr(visitor, state, node.value)
			visitor.visit_table_field_no_key_end(state, node)
		else
			unreachable(node.kind)
		end

		visitor.visit_table_field_end(state, node)

		if sep then
			visit_token(visitor, state, sep)
		end
	end

	visit_token(visitor, state, node.braces.close)
	visitor.visit_table_end(state, node)
end

function visit_function_body<State>(visitor: Visitor<State>, state: State, node: cst.FunctionBody)
	visitor.visit_function_body(state, node)

	local generics = node.generics
	if generics then
		visit_generic_declaration(visitor, state, generics)
	end

	visit_token(visitor, state, node.parens.open)

	for _, pair in node.params do
		local node, sep = pair.node, pair.sep

		visitor.visit_binding(state, node)
		visit_token(visitor, state, node.name)

		local type = node.type
		if type then
			visit_token(visitor, state, type.colon)
			visit_type(visitor, state, type.type)
		end

		visitor.visit_binding_end(state, node)

		if sep then
			visit_token(visitor, state, sep)
		end
	end

	visit_token(visitor, state, node.parens.close)

	local varargs = node.varargs
	if varargs then
		visit_token(visitor, state, varargs.dots)

		local type = varargs.type
		if type then
			visit_token(visitor, state, type.colon)

			local type = type.type
			if type.kind == "generic" then
				visitor.visit_generic_type_pack(state, type)
				visit_token(visitor, state, type.name)
				visit_token(visitor, state, type.dots)
				visitor.visit_generic_type_pack_end(state, type)
			else
				visit_type(visitor, state, type)
			end
		end
	end

	local ret = node.ret
	if ret then
		visit_token(visitor, state, ret.colon)
		visit_return_type(visitor, state, ret.type)
	end

	visit_block(visitor, state, node.block)
	visit_token(visitor, state, node.end_tok)
	visitor.visit_function_body_end(state, node)
end

function visit_stat<State>(visitor: Visitor<State>, state: State, node: cst.Stat)
	visitor.visit_stat(state, node)

	if node.kind == "assign" then
		node = node :: cst.Stat_Assign
		visitor.visit_stat_assign(state, node)

		for _, pair in node.left do
			local node, sep = pair.node, pair.sep

			visit_var(visitor, state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visit_token(visitor, state, node.equals)

		for _, pair in node.right do
			local node, sep = pair.node, pair.sep

			visit_expr(visitor, state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visitor.visit_stat_assign_end(state, node)
	elseif node.kind == "compoundassign" then
		node = node :: cst.Stat_CompoundAssign
		visitor.visit_stat_compound_assign(state, node)
		visit_var(visitor, state, node.left)
		visit_token(visitor, state, node.operator)
		visit_expr(visitor, state, node.right)
		visitor.visit_stat_compound_assign_end(state, node)
	elseif node.kind == "call" then
		visitor.visit_stat_call(state, node)
		visit_var(visitor, state, node.call)
		visitor.visit_stat_call_end(state, node)
	elseif node.kind == "do" then
		visitor.visit_stat_do(state, node)
		visit_token(visitor, state, node.do_tok)
		visit_block(visitor, state, node.block)
		visit_token(visitor, state, node.end_tok)
		visitor.visit_stat_do_end(state, node)
	elseif node.kind == "while" then
		visitor.visit_stat_while(state, node)
		visit_token(visitor, state, node.while_tok)
		visit_expr(visitor, state, node.condition)
		visit_token(visitor, state, node.do_tok)
		visit_block(visitor, state, node.block)
		visit_token(visitor, state, node.end_tok)
		visitor.visit_stat_while_end(state, node)
	elseif node.kind == "repeat" then
		visitor.visit_stat_repeat(state, node)
		visit_token(visitor, state, node.repeat_tok)
		visit_block(visitor, state, node.block)
		visit_token(visitor, state, node.until_tok)
		visit_expr(visitor, state, node.condition)
		visitor.visit_stat_repeat_end(state, node)
	elseif node.kind == "if" then
		visitor.visit_stat_if(state, node)
		visit_token(visitor, state, node.if_tok)

		for _, pair in node.branches do
			local node, sep = pair.node, pair.sep

			visitor.visit_if_stat_branch(state, node)

			visit_expr(visitor, state, node.condition)
			visit_token(visitor, state, node.then_tok)
			visit_block(visitor, state, node.block)

			visitor.visit_if_stat_branch_end(state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		local else_branch = node.else_branch
		if else_branch then
			visit_token(visitor, state, else_branch.else_tok)
			visit_block(visitor, state, else_branch.block)
		end

		visit_token(visitor, state, node.end_tok)
		visitor.visit_stat_if_end(state, node)
	elseif node.kind == "numericfor" then
		visitor.visit_stat_numeric_for(state, node)
		visit_token(visitor, state, node.for_tok)

		local binding = node.binding
		visitor.visit_binding(state, binding)
		visit_token(visitor, state, binding.name)

		local type = binding.type
		if type then
			visit_token(visitor, state, type.colon)
			visit_type(visitor, state, type.type)
		end

		visitor.visit_binding_end(state, binding)

		visit_token(visitor, state, node.equals)
		visit_expr(visitor, state, node.start_expr)
		visit_token(visitor, state, node.comma)
		visit_expr(visitor, state, node.end_expr)

		local step = node.step
		if step then
			visit_token(visitor, state, step.comma)
			visit_expr(visitor, state, step.expr)
		end

		visit_token(visitor, state, node.do_tok)
		visit_block(visitor, state, node.block)
		visit_token(visitor, state, node.end_tok)
		visitor.visit_stat_numeric_for_end(state, node)
	elseif node.kind == "forin" then
		node = node :: cst.Stat_ForIn
		visitor.visit_stat_for_in(state, node)
		visit_token(visitor, state, node.for_tok)

		for _, pair in node.bindings do
			local node, sep = pair.node, pair.sep

			visitor.visit_binding(state, node)
			visit_token(visitor, state, node.name)

			local type = node.type
			if type then
				visit_token(visitor, state, type.colon)
				visit_type(visitor, state, type.type)
			end

			visitor.visit_binding_end(state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visit_token(visitor, state, node.in_tok)

		for _, pair in node.exprs do
			local node, sep = pair.node, pair.sep

			visit_expr(visitor, state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visit_token(visitor, state, node.do_tok)
		visit_block(visitor, state, node.block)
		visit_token(visitor, state, node.end_tok)
		visitor.visit_stat_for_in_end(state, node)
	elseif node.kind == "function" then
		visitor.visit_stat_function(state, node)

		for _, tok in node.attributes do
			visit_token(visitor, state, tok)
		end

		visit_token(visitor, state, node.function_tok)

		for _, pair in node.names do
			local node, sep = pair.node, pair.sep

			visit_token(visitor, state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		local method = node.method
		if method then
			visit_token(visitor, state, method.colon)
			visit_token(visitor, state, method.name)
		end

		visit_function_body(visitor, state, node.body)
		visitor.visit_stat_function_end(state, node)
	elseif node.kind == "localfunction" then
		node = node :: cst.Stat_LocalFunction
		visitor.visit_stat_local_function(state, node)

		for _, tok in node.attributes do
			visit_token(visitor, state, tok)
		end

		visit_token(visitor, state, node.local_tok)
		visit_token(visitor, state, node.function_tok)
		visit_token(visitor, state, node.name)
		visit_function_body(visitor, state, node.body)
		visitor.visit_stat_local_function_end(state, node)
	elseif node.kind == "typefunction" then
		node = node :: cst.Stat_TypeFunction

		visitor.visit_stat_type_function(state, node)

		local export_tok = node.export_tok
		if export_tok then
			visit_token(visitor, state, export_tok)
		end

		visit_token(visitor, state, node.type_tok)
		visit_token(visitor, state, node.function_tok)
		visit_token(visitor, state, node.name)

		visit_function_body(visitor, state, node.body)
		visitor.visit_stat_type_function_end(state, node)
	elseif node.kind == "localvariable" then
		node = node :: cst.Stat_LocalVariable
		visitor.visit_stat_local_variable(state, node)
		visit_token(visitor, state, node.local_tok)

		for _, pair in node.bindings do
			local node, sep = pair.node, pair.sep

			visit_token(visitor, state, node.name)

			local type = node.type
			if type then
				visit_token(visitor, state, type.colon)
				visit_type(visitor, state, type.type)
			end

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		local values = node.values
		if values then
			visit_token(visitor, state, values.equals)

			for _, pair in values.exprs do
				local node, sep = pair.node, pair.sep

				visit_expr(visitor, state, node)

				if sep then
					visit_token(visitor, state, sep)
				end
			end
		end

		visitor.visit_stat_local_variable_end(state, node)
	elseif node.kind == "type" then
		node = node :: cst.Stat_Type
		visitor.visit_stat_type(state, node)

		local export_tok = node.export_tok
		if export_tok then
			visit_token(visitor, state, export_tok)
		end

		visit_token(visitor, state, node.type_tok)
		visit_token(visitor, state, node.name)

		local generics = node.generics
		if generics then
			visit_generic_declaration_with_defaults(visitor, state, generics)
		end

		visit_token(visitor, state, node.equals)
		visit_type(visitor, state, node.type)
		visitor.visit_stat_type_end(state, node)
	end

	visitor.visit_stat_end(state, node)
end

function visit_last_stat<State>(visitor: Visitor<State>, state: State, node: cst.LastStat)
	visitor.visit_last_stat(state, node)

	if node.kind == "return" then
		visitor.visit_last_stat_return(state, node)
		visit_token(visitor, state, node.return_tok)

		for _, pair in node.exprs do
			local node, sep = pair.node, pair.sep

			visit_expr(visitor, state, node)

			if sep then
				visit_token(visitor, state, sep)
			end
		end

		visitor.visit_last_stat_return_end(state, node)
	elseif node.kind == "break" then
		visitor.visit_last_stat_break(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_last_stat_break_end(state, node)
	elseif node.kind == "continue" then
		visitor.visit_last_stat_continue(state, node)
		visit_token(visitor, state, node.tok)
		visitor.visit_last_stat_continue_end(state, node)
	else
		unreachable(node.kind)
	end

	visitor.visit_last_stat_end(state, node)
end

function visit_block<State>(visitor: Visitor<State>, state: State, block: cst.Block)
	visitor.visit_block(state, block)

	for _, pair in block.stats do
		local node, sep = pair.node, pair.sep

		visit_stat(visitor, state, node)

		if sep then
			visit_token(visitor, state, sep)
		end
	end

	local last_stat = block.last_stat
	if last_stat then
		visit_last_stat(visitor, state, last_stat.stat)

		local semicolon = last_stat.semicolon
		if semicolon then
			visit_token(visitor, state, semicolon)
		end
	end

	visitor.visit_block_end(state, block)
end

local function visit<State>(visitor: Visitor<State>, state: State, node: cst.Cst)
	visitor.visit_cst(state, node)
	visit_block(visitor, state, node.block)
	visit_token(visitor, state, node.eof)
	visitor.visit_cst_end(state, node)
end

return {
	visitor = visitor,
	visit = visit,
}


--!optimize 2
--!strict

local plugin = script:FindFirstAncestorOfClass("Plugin")

local StudioService = game:GetService("StudioService")

local RawInst = require(script.Parent.Small.RawInst)
local index, newindex = RawInst.index, RawInst.newindex

local FIXED_DISTANCE = 140
local PADDING = 4
local EQUALS_WIDTH = 16

export type ItemList = {
	Backdrop: Frame & any,
	Container: ScrollingFrame,
	CurrentParent: ItemList?,
	Items: {GuiObject},
	IsShown: boolean,
	AddItem: (self: ItemList, item: GuiObject) -> (),
	AddBulkItem: (self: ItemList, items: {GuiObject}) -> (),
	RemoveItem: (self: ItemList, item: GuiObject) -> (),
	Clear: (self: ItemList) -> (),
	SetAnchor: (self: ItemList, anchorPoint: Vector2, position: UDim2) -> (),
	Show: (self: ItemList) -> (),
	Hide: (self: ItemList) -> (),
	Destroy: (self: ItemList) -> (),
	Parent: (self: ItemList, parentList: ItemList) -> ()
}

export type ValueUIItem<T = (newValue: string, ...never) -> ()> = {
	Instance: Instance & {
		ValueBox: TextBox
	},
	Get: (self: ValueUIItem<T>) -> any,
	Set: (self: ValueUIItem<T>, value: any) -> (),
	SetText: (self: ValueUIItem<T>, new: string) -> (),
	GetName: (self: ValueUIItem<T>) -> string,
	SetName: (self: ValueUIItem<T>, new: string) -> (),
	OnChange: (self: ValueUIItem<T>, callback: T) -> RBXScriptConnection,
	Destroy: (self: ValueUIItem<T>) -> (),
	SetEditable: (self: ValueUIItem<T>, editable: boolean) -> ()
}

export type TriggerUIItem = {
	Instance: Instance & any,
	Trigger: (self: TriggerUIItem) -> (),
	OnTrigger: (self: TriggerUIItem, callback: () -> ()) -> RBXScriptConnection,
	Destroy: (self: TriggerUIItem) -> ()
}

local function remove(t: any, key: any)
	local v = t[key]
	t[key] = nil
	return v
end

local updateLayoutQueue = {}
local layoutQueueDirty = false
local queueProcessing = false
local function updateLayout(container: any)
	table.insert(updateLayoutQueue, container)
	layoutQueueDirty = true
	task.defer(function()
		if queueProcessing then return end
		if not layoutQueueDirty then return end
		queueProcessing = true
		layoutQueueDirty = false
		-- Update canvas size based on content
		for _, container in updateLayoutQueue do
			local totalHeight = 0
			for _, child in container:GetChildren() do
				if child:IsA("GuiObject") and child.Visible then
					totalHeight += child.AbsoluteSize.Y
				end
			end
			container.CanvasSize = UDim2.new(0, 0, 0, totalHeight + 2)
		end
		table.clear(updateLayoutQueue)
		queueProcessing = false
	end)
end

local function isNotTransparent(gui: GuiObject)
	if gui:IsA("Frame") then
		return gui.BackgroundTransparency < 0.98
	elseif
		gui:IsA("TextButton")
	or	gui:IsA("TextLabel")
	or	gui:IsA("TextBox")
	then
		return gui.BackgroundTransparency < 0.98 or gui.Transparency < 0.98
	else
		return true
	end
end

local function canBeSeen(gui: GuiObject)
	local isntTransparent = isNotTransparent(gui)

	if isntTransparent then
		return true
	else
		return gui:FindFirstChildOfClass("UIStroke") ~= nil
	end
end

local PluginUI = {}

function PluginUI.getGuiObjectsAtPosition(root: GuiObject, x: number, y: number)
	local list: {GuiObject} = {}
	local visit, iterate

	function visit(instance: Instance, clipStart: Vector2?, clipEnd: Vector2?)
		if instance:IsA("GuiObject") then
			if not instance.Visible then return false end

			local start = if clipStart then
				Vector2.new(
					math.max(instance.AbsolutePosition.X, clipStart.X),
					math.max(instance.AbsolutePosition.Y, clipStart.Y)
				)
				else
				instance.AbsolutePosition

			local extend = if clipEnd then
				Vector2.new(
					math.min(
						instance.AbsolutePosition.X + instance.AbsoluteSize.X,
						clipEnd.X
					),
					math.min(
						instance.AbsolutePosition.Y + instance.AbsoluteSize.Y,
						clipEnd.Y
					)
				)
				else
				instance.AbsolutePosition + instance.AbsoluteSize

			if instance.ClipsDescendants then
				iterate(instance, start, extend)
			else
				iterate(instance, clipStart, clipEnd)
			end

			local minX = math.min(start.X, extend.X)
			local maxX = math.max(minX, extend.X)
			local minY = math.min(start.Y, extend.Y)
			local maxY = math.max(minY, extend.Y)

			local isInside =
				x >= minX
			and x <= maxX
			and	y >= minY
			and	y <= maxY

			return isInside;
		end
		iterate(instance, clipStart, clipEnd)
		return false
	end

	function iterate(instance: Instance, clipStart: Vector2?, clipEnd: Vector2?)
		local buffered: {{Index: number, Object: GuiObject}} = {}
		local children = instance:GetChildren()

		for i, child in children do
			if not child:IsA("GuiObject") then continue end
			table.insert(buffered, {
				Index = i,
				Object = child
			})
		end

		table.sort(buffered, function(a, b)
			if a.Object.ZIndex == b.Object.ZIndex then
				return a.Index > b.Index
			else
				return a.Object.ZIndex > b.Object.ZIndex
			end
		end)

		for _, child in buffered do
			local isInside = visit(child.Object, clipStart, clipEnd)
			if isInside and canBeSeen(child.Object) then
				table.insert(list, child.Object)
			end
		end
	end

	iterate(root)
	return list
end

local function connection(tbl: {any}, item: any): RBXScriptConnection
	return {
		Connected = true,
		Disconnect = function(self)
			if not self.Connected then return end

			self.Connected = false
			local idx = table.find(tbl, item)
			if idx then
				table.remove(tbl, idx)
			end
		end,
	} :: any
end

function PluginUI.getId(name: string, unique: boolean?)
	if unique then
		return name .. os.clock()
	end
	return name
end

function PluginUI.createEditorWidget<T>(mainframe: T, name: string, displayName: string, guiInfo: DockWidgetPluginGuiInfo?): (DockWidgetPluginGui, T)
	assert(typeof(mainframe) == "Instance")

	local widget = plugin:CreateDockWidgetPluginGui(
		PluginUI.getId(name, true), guiInfo or DockWidgetPluginGuiInfo.new(
			Enum.InitialDockState.Float,
			false,
			true,
			500,
			300
		)
	)
	widget:AddTag("JEXP_Widget")

	widget.Name = `Jexplorer_{name}`
	widget.Title = `Jexplorer {displayName}`
	widget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	widget:BindToClose(function()
		widget.Enabled = not widget.Enabled
	end)

	mainframe:AddTag("JEXP_Background")
	mainframe.Parent = widget

	mainframe.Items.Content:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
		local size = mainframe.Items.Content.AbsoluteSize
		mainframe.Items.Overlay.Size = UDim2.fromOffset(size.X, size.Y)
	end)

	return widget, mainframe
end

function PluginUI.createItemListFromExisting(backdrop: Instance, container: Instance, scrollingFrame: ScrollingFrame): ItemList
	local items = {}

	local itemList = {
		Backdrop = backdrop,
		Container = container,
		Items = items
	}

	-- Methods
	function itemList:AddItem(item: GuiObject)
		item.Parent = container
		item.Size = UDim2.new(1, 0, 0, item.Size.Y.Offset or 30)
		table.insert(self.Items, item)
		updateLayout(scrollingFrame)
	end

	function itemList:AddBulkItem(items: {GuiObject})
		for _, item in items do
			item.Parent = container
			table.insert(self.Items, item)
			item.Size = UDim2.new(1, 0, 0, item.Size.Y.Offset)
		end

		updateLayout(scrollingFrame)
	end

	function itemList:RemoveItem(item: GuiObject)
		for i, existingItem in self.Items do
			if existingItem == item then
				table.remove(self.Items, i)
				item:Destroy()
				break
			end
		end
		updateLayout(scrollingFrame)
	end

	function itemList:Clear()
		for _, item in self.Items do
			item:Destroy()
		end
		self.Items = {}
		updateLayout(scrollingFrame)
	end

	function itemList:SetAnchor(anchorPoint: Vector2, position: UDim2)
		self.Backdrop.AnchorPoint = anchorPoint
		self.Backdrop.Position = position
	end

	function itemList:Show()
		self.Backdrop.Visible = true
	end

	function itemList:Hide()
		self.Backdrop.Visible = false
	end

	function itemList:Destroy()
		self.Backdrop:Destroy()
	end

	function itemList:Parent(parent)
		self.CurrentParent = parent
		backdrop.Parent = parent and parent.Container or nil
	end

	return itemList :: any
end

function PluginUI.createItemList(frameProperties: {[string]: any}?): ItemList
	local backdrop do
		backdrop = Instance.new("Frame")
		local parent
		if frameProperties then
			parent = remove(frameProperties, "Parent")
		end

		-- Default properties for backdrop
		backdrop.Name = "ItemListBackdrop"
		backdrop:AddTag("JEXP_Background")
		backdrop:AddTag("JEXP_Border")

		backdrop.BorderSizePixel = 0
		backdrop.Size = UDim2.new(0, 200, 0, 300)
		backdrop.Position = UDim2.new(0, 0, 0, 0)
		backdrop.AnchorPoint = Vector2.new(0, 0)
		backdrop.ZIndex = 1000
		backdrop.Visible = false

		if frameProperties then
			-- Apply custom properties
			for k, v in frameProperties do
				newindex(backdrop, k, v)
			end
		end

		if parent then backdrop.Parent = parent end
	end

	-- Create scrolling container
	local container = Instance.new("ScrollingFrame")
	container.Name = "ItemContainer"

	container.Parent = backdrop
	container.Size = UDim2.new(1, -4, 1, -4)
	container.Position = UDim2.new(0, 2, 0, 2)
	container.BackgroundTransparency = 1
	container.BorderSizePixel = 0
	container.ScrollBarThickness = 6
	container.ScrollBarImageColor3 = Color3.fromRGB(85, 85, 85)
	container.CanvasSize = UDim2.new(0, 0, 0, 0)
	container.AutomaticCanvasSize = Enum.AutomaticSize.Y

	-- Add UIListLayout for automatic positioning
	local layout = Instance.new("UIListLayout")
	layout.Parent = container
	layout.SortOrder = Enum.SortOrder.LayoutOrder
	layout.Padding = UDim.new(0, 1)

	-- Track items
	local items = {}

	local itemList = {
		Backdrop = backdrop,
		Container = container,
		Items = items,
		IsShown = false
	}

	-- Methods
	function itemList:AddItem(item: GuiObject)
		item.Parent = container
		table.insert(self.Items, item)
		updateLayout(container)
	end

	function itemList:AddBulkItem(items: {GuiObject})
		for _, item in items do
			item.Parent = container
			table.insert(self.Items, item)
		end

		updateLayout(container)
	end

	function itemList:RemoveItem(item: GuiObject)
		for i, existingItem in self.Items do
			if existingItem == item then
				table.remove(self.Items, i)
				item:Destroy()
				break
			end
		end
		updateLayout(container)
	end

	function itemList:Clear()
		for _, item in self.Items do
			item:Destroy()
		end
		self.Items = {}
		updateLayout(container)
	end

	function itemList:SetAnchor(anchorPoint: Vector2, position: UDim2)
		self.Backdrop.AnchorPoint = anchorPoint
		self.Backdrop.Position = position
	end

	function itemList:Show()
		self.Backdrop.Visible = true
		self.IsShown = true
	end

	function itemList:Hide()
		self.Backdrop.Visible = false
		self.IsShown = false
	end

	function itemList:Destroy()
		self.Backdrop:Destroy()
	end

	function itemList:Parent(parent)
		self.CurrentParent = parent
		backdrop.Parent = parent and parent.Container or nil
	end

	return itemList :: any
end

type DropdownList = ItemList & {SetText: (DropdownList, txt: string) -> ()}

function PluginUI.createDropDownItemList(frameProperties: {[string]: any}?, headerText: string?, headerLabelIsBox: boolean?, headerHeight: number?): DropdownList
	local HEADER_HEIGHT = headerHeight or 20
	local INDENT_WIDTH = 16

	local backdrop do
		backdrop = Instance.new("Frame")
		local parent
		if frameProperties then
			parent = remove(frameProperties, "Parent")
		end

		-- Default properties for backdrop
		backdrop.Name = "DropDownItemListBackdrop"
		backdrop:AddTag("JEXP_Background")
		--backdrop:AddTag("JEXP_Border")

		backdrop.BorderSizePixel = 0
		backdrop.Size = UDim2.new(1, 0, 0, HEADER_HEIGHT)
		backdrop.Position = UDim2.new(0, 0, 0, 0)
		backdrop.AnchorPoint = Vector2.new(0, 0)
		backdrop.ZIndex = 1000
		backdrop.AutomaticSize = Enum.AutomaticSize.Y
		backdrop.Visible = false

		if frameProperties then
			for k, v in frameProperties do
				newindex(backdrop, k, v)
			end
		end

		if parent then backdrop.Parent = parent end
	end

	-- Create header
	local header = Instance.new("Frame")
	header.Name = "DropDownHeader"
	header:AddTag("JEXP_LightBackground")
	header:AddTag("JEXP_Hover")

	header.BorderSizePixel = 0
	header.Size = UDim2.new(1, -2, 0, HEADER_HEIGHT - 2)
	header.Position = UDim2.new(0, 1, 0, 1)
	header.Parent = backdrop

	-- Arrow icon
	local arrow = Instance.new("ImageLabel")
	arrow.Name = "Arrow"
	arrow:AddTag("JEXP_Arrow")

	arrow.Size = UDim2.new(0, 12, 0, 12)
	arrow.Position = UDim2.new(0, 10, 0.5, 0)
	arrow.AnchorPoint = Vector2.new(0.5, 0.5)
	arrow.BackgroundTransparency = 1
	arrow.Rotation = -90
	arrow.Parent = header

	local headerLabel: any
	if headerLabelIsBox then
		headerLabel = Instance.new("TextBox")
		headerLabel.ClearTextOnFocus = false
	else
		headerLabel = Instance.new("TextLabel")
	end
	headerLabel.Name = "HeaderLabel"
	headerLabel:AddTag("JEXP_Text")

	headerLabel.ZIndex = 3
	headerLabel.TextTruncate = Enum.TextTruncate.AtEnd
	headerLabel.BorderSizePixel = 0
	headerLabel.AnchorPoint = Vector2.new(0, 0.5)
	headerLabel.Size = UDim2.new(0.5, 0, 1, 0)
	headerLabel.Position = UDim2.new(0, 24, 0.5, 0)
	headerLabel.BackgroundTransparency = 1
	headerLabel.Text = headerText or "Dropdown"
	headerLabel.TextXAlignment = Enum.TextXAlignment.Left
	headerLabel.Font = Enum.Font.SourceSans
	headerLabel.TextSize = math.max(10, HEADER_HEIGHT * 0.65)
	headerLabel.Parent = header

	-- Create scrolling container
	local container = Instance.new("ScrollingFrame")
	container.Name = "DropDownContainer"

	container.VerticalScrollBarInset = Enum.ScrollBarInset.Always
	container.HorizontalScrollBarInset = Enum.ScrollBarInset.Always
	container.BorderSizePixel = 0
	container.Size = UDim2.new(1, -INDENT_WIDTH, 0, 0)
	container.Position = UDim2.new(0, INDENT_WIDTH, 0, HEADER_HEIGHT)
	container.BackgroundTransparency = 1
	container.BorderSizePixel = 0
	container.ScrollBarThickness = 6
	container.ScrollBarImageColor3 = Color3.fromRGB(85, 85, 85)
	container.CanvasSize = UDim2.new(0, 0, 0, 0)
	container.AutomaticCanvasSize = Enum.AutomaticSize.None
	container.AutomaticSize = Enum.AutomaticSize.Y
	container.Visible = false -- Start collapsed
	container.Parent = backdrop

	local layout = Instance.new("UIListLayout")
	layout.Parent = container
	layout.SortOrder = Enum.SortOrder.LayoutOrder
	layout.Padding = UDim.new(0, 1)

	local items = {}
	local itemList = {
		Backdrop = backdrop,
		Container = container,
		Items = items,
		IsShown = false
	} :: DropdownList

	function itemList:AddItem(item: GuiObject)
		item.Parent = container
		item.Size = UDim2.new(1, 0, 0, item.Size.Y.Offset)
		table.insert(self.Items, item)
		updateLayout(container)
	end

	function itemList:SetText(txt)
		headerLabel.Text = txt
	end

	function itemList:AddBulkItem(items: {GuiObject})
		for _, item in items do
			item.Parent = container
			table.insert(self.Items, item)
			item.Size = UDim2.new(1, 0, 0, item.Size.Y.Offset)
		end

		updateLayout(container)
	end

	function itemList:RemoveItem(item: GuiObject)
		for i, existingItem in self.Items do
			if existingItem == item then
				table.remove(self.Items, i)
				item:Destroy()
				break
			end
		end
		updateLayout(container)
	end

	function itemList:Clear()
		for _, item in self.Items do
			item:Destroy()
		end
		self.Items = {}
		updateLayout(container)
	end

	function itemList:SetAnchor(anchorPoint: Vector2, position: UDim2)
		self.Backdrop.AnchorPoint = anchorPoint
		self.Backdrop.Position = position
	end

	local lastContentHeight = 0
	-- Show = Expand
	function itemList:Show()
		self.Backdrop.Visible = true
		if self.IsShown then return end

		self.IsShown = true
		container.Visible = true

		self.Container.AutomaticCanvasSize = Enum.AutomaticSize.Y

		arrow.Rotation = 0
	end

	-- Hide = Collapse
	function itemList:Hide()
		if not self.IsShown then
			self.Backdrop.Visible = false
			return
		end

		self.IsShown = false

		self.Container.AutomaticCanvasSize = Enum.AutomaticSize.None

		arrow.Rotation = -90
		container.Visible = false
	end

	function itemList:Destroy()
		self.Backdrop:Destroy()
	end

	function itemList:Parent(parent)
		self.CurrentParent = parent
		backdrop.Parent = parent and parent.Container or nil
	end
	local headerButton = Instance.new("TextButton")
	headerButton.Parent = header
	headerButton.Size = UDim2.new(1, 0, 1, 0)
	headerButton.BackgroundTransparency = 1
	headerButton.Text = ""

	headerButton.MouseButton1Click:Connect(function()
		if itemList.IsShown then
			itemList:Hide()
		else
			itemList:Show()
		end
	end)

	return itemList :: any
end

function PluginUI.createButtonWithIcon(list: ItemList?, text: string, icon: string, imageRectOffset: Vector2, imageRectSize: Vector2, callback: (() -> ())?): TriggerUIItem
	local button = Instance.new("Frame")
	button:AddTag("JEXP_Background")
	button:AddTag("JEXP_Hover")

	button.Name = "ButtonWithIconItem"
	button.Size = UDim2.new(1, 0, 0, 20)
	button.BorderSizePixel = 0

	local iconLabel = Instance.new("ImageLabel")
	iconLabel.Name = "Icon"
	iconLabel.Size = UDim2.new(0, 16, 0, 16)
	iconLabel.Position = UDim2.new(0, 8, 0.5, -8)
	iconLabel.BackgroundTransparency = 1
	iconLabel.Image = icon
	iconLabel.ImageRectSize = imageRectSize
	iconLabel.ImageRectOffset = imageRectOffset
	iconLabel.Parent = button

	-- Label
	local label = Instance.new("TextLabel")
	label.Name = "Label"
	label:AddTag("JEXP_Text")

	label.Size = UDim2.new(1, -32, 1, 0)
	label.Position = UDim2.new(0, 32, 0, 0)
	label.BackgroundTransparency = 1
	label.Text = text or "Button"
	label.TextXAlignment = Enum.TextXAlignment.Left
	label.Font = Enum.Font.SourceSans
	label.TextSize = 13
	label.Parent = button

	-- Click handling
	local clickButton = Instance.new("TextButton")
	clickButton.Name = "Click"
	clickButton.Size = UDim2.new(1, 0, 1, 0)
	clickButton.BackgroundTransparency = 1
	clickButton.Text = ""
	clickButton.Parent = button

	if list then
		list:AddItem(button)
	end

	local callbacks: {any} = {callback}
	local item: TriggerUIItem = {
		Instance = button,
		Trigger = function(self)
			for _, v in callbacks do
				v()
			end
		end,
		OnTrigger = function(self, fn)
			table.insert(callbacks, fn)
			return connection(callbacks, fn)
		end,
		Destroy = function(self)
			table.clear(callbacks)
			table.clear(self)
			button:Destroy()
		end,
	}

	clickButton.Activated:Connect(function()
		for _, v in callbacks do
			v()
		end
	end)

	return item
end

function PluginUI.createCheckBox(list: ItemList?, text: string?, checked: boolean?, callback: ((boolean) -> ())?): ValueUIItem
	local checkBox = Instance.new("Frame")
	checkBox.Name = "CheckBoxItem"
	checkBox.BackgroundTransparency = 1

	checkBox.Size = UDim2.new(1, 0, 0, 20)
	checkBox.BorderSizePixel = 0

	local checkBoxVisual = Instance.new("Frame")
	checkBoxVisual.Name = "CheckBox"
	checkBoxVisual:AddTag("JEXP_LightBackground")
	checkBoxVisual:AddTag("JEXP_Border")

	checkBoxVisual.Parent = checkBox
	checkBoxVisual.Size = UDim2.new(0, 14, 0, 14)
	checkBoxVisual.AnchorPoint = Vector2.new(0, 0.5)
	checkBoxVisual.Position = UDim2.new(0.55, 0, 0.5, 0)
	checkBoxVisual.BorderSizePixel = 0

	local checkMark = Instance.new("TextLabel")
	checkMark.Name = "CheckMark"

	checkMark.Parent = checkBoxVisual
	checkMark.Size = UDim2.new(1, 0, 1, 0)
	checkMark.BackgroundTransparency = 1
	checkMark.Text = "T"
	checkMark.TextScaled = true
	checkMark.Font = Enum.Font.SourceSansBold
	checkMark.Visible = checked or false

	if checked then
		checkBoxVisual:AddTag("JEXP_SelectedColor")
		checkMark:AddTag("JEXP_SelectedColor")
	end

	local label = Instance.new("TextLabel")
	label.Name = "Label"
	label:AddTag("JEXP_Text")

	label.Parent = checkBox
	label.Size = UDim2.new(1, -32, 1, 0)
	label.Position = UDim2.new(0.05, 0, 0, 0)
	label.BackgroundTransparency = 1
	label.Text = text or "Checkbox"
	label.TextXAlignment = Enum.TextXAlignment.Left
	label.Font = Enum.Font.SourceSans
	label.TextSize = 12

	local isChecked = checked or false
	local button = Instance.new("TextButton")
	button.Parent = checkBox
	button.Size = UDim2.new(1, 0, 1, 0)
	button.BackgroundTransparency = 1
	button.Text = ""

	if list then
		list:AddItem(checkBox)
	end

	local callbacks: {any} = {callback}

	local item: ValueUIItem = {
		Instance = checkBox :: any,
		Get = function(self)
			return isChecked
		end,
		Set = function(self, v)
			isChecked = v
			checkMark.Visible = v
			if isChecked then
				checkBoxVisual:AddTag("JEXP_SelectedColor")
				checkMark:AddTag("JEXP_SelectedColor")
			else
				checkBoxVisual:RemoveTag("JEXP_SelectedColor")
				checkMark:RemoveTag("JEXP_SelectedColor")
			end
		end,
		SetText = function(self, new)
			local is = new == "true"
			checkMark.Visible = is
			isChecked = is
			if isChecked then
				checkBoxVisual:AddTag("JEXP_SelectedColor")
				checkMark:AddTag("JEXP_SelectedColor")
			else
				checkBoxVisual:RemoveTag("JEXP_SelectedColor")
				checkMark:RemoveTag("JEXP_SelectedColor")
			end
		end,
		OnChange = function(self, fn)
			table.insert(callbacks, fn)
			return connection(callbacks, fn)
		end,
		Destroy = function(self)
			table.clear(callbacks)
			table.clear(self)
			checkBox:Destroy()
		end,
		SetEditable = function(self, editable)
			if not editable then
				label:RemoveTag("JEXP_Text")
				label:AddTag("JEXP_FadedText")
				checkBoxVisual:RemoveTag("JEXP_LightBackground")
				checkBoxVisual:AddTag("JEXP_DarkBackground")
				checkMark:RemoveTag("JEXP_Text")
				checkMark:AddTag("JEXP_FadedText")
				button.Interactable = false
				button.Active = false
				return
			end
			label:AddTag("JEXP_Text")
			label:RemoveTag("JEXP_FadedText")
			checkBoxVisual:AddTag("JEXP_LightBackground")
			checkBoxVisual:RemoveTag("JEXP_DarkBackground")
			checkMark:AddTag("JEXP_Text")
			checkMark:RemoveTag("JEXP_FadedText")
			button.Interactable = true
			button.Active = true
		end,
		GetName = function(self)
			return label.Text
		end,
		SetName = function(self, new)
			label.Text = new
		end,
	}

	button.MouseButton1Click:Connect(function()
		isChecked = not isChecked
		checkMark.Visible = isChecked
		if isChecked then
			checkBoxVisual:AddTag("JEXP_SelectedColor")
			checkMark:AddTag("JEXP_SelectedColor")
		else
			checkBoxVisual:RemoveTag("JEXP_SelectedColor")
			checkMark:RemoveTag("JEXP_SelectedColor")
		end

		for _, v in callbacks do
			v(isChecked)
		end
	end)

	local function updateValueBox()
		local nameLabelLocalX = label.AbsolutePosition.X - checkBox.AbsolutePosition.X
		local textRightEdge = nameLabelLocalX + label.TextBounds.X + PADDING
		local fixedEdge = nameLabelLocalX + FIXED_DISTANCE + PADDING

		local x = math.max(fixedEdge, textRightEdge)

		checkBoxVisual.Position = UDim2.new(0, x, 0.5, 0)
	end

	label:GetPropertyChangedSignal("TextBounds"):Connect(updateValueBox)
	label:GetPropertyChangedSignal("AbsolutePosition"):Connect(updateValueBox)
	task.defer(updateValueBox)

	return item
end

function PluginUI.createButton(list: ItemList?, text: string?, callback: (() -> ())?, isimage: boolean?): TriggerUIItem
	local isimage = if isimage == nil then false else isimage
	local button = Instance.new("Frame")
	button:AddTag("JEXP_Background")
	button:AddTag("JEXP_Hover")

	button.BorderSizePixel = 0
	button.Name = "ButtonItem"
	button.Size = UDim2.new(1, 0, 0, 20)

	-- Label
	local label = Instance.new(isimage and "ImageLabel" or "TextLabel")
	label.Name = "Label"
	label:AddTag("JEXP_Text")

	label.BorderSizePixel = 0
	--label.Size = UDim2.new(1, -16, 1, 0)
	label.Size = UDim2.new(0, 0, 1, 0)
	label.Position = UDim2.new(0, 22, 0, 0)
	label.AutomaticSize = Enum.AutomaticSize.X
	label.BackgroundTransparency = 1
	if isimage then
		label.Image = text or ""
	else
		label.Text = text or "Button"
		label.TextXAlignment = Enum.TextXAlignment.Left
		label.Font = Enum.Font.SourceSans
		label.TextSize = 13
	end
	label.Parent = button

	local cursorIcon = Instance.new("ImageLabel")
	cursorIcon.Name = "CursorInterest"
	cursorIcon.Image = "rbxassetid://12804017021"
	cursorIcon:AddTag("JEXP_MonochromeImage")
	cursorIcon.Size = UDim2.fromOffset(14, 16)
	cursorIcon.Position = UDim2.new(0, 4, 0.5, 0)
	cursorIcon.AnchorPoint = Vector2.new(0, 0.5)
	cursorIcon.BackgroundTransparency = 1
	cursorIcon.Parent = button

	-- Click handling
	local clickButton = Instance.new("TextButton")
	clickButton.BorderSizePixel = 0
	clickButton.Size = UDim2.new(1, 0, 1, 0)
	clickButton.BackgroundTransparency = 1
	clickButton.Text = ""
	clickButton.ZIndex = 2
	clickButton.Parent = button

	if list then
		list:AddItem(button)
	end

	local callbacks: {any} = {callback}
	local item: TriggerUIItem = {
		Instance = button,
		Trigger = function(self)
			for _, v in callbacks do
				v()
			end
		end,
		OnTrigger = function(self, fn)
			table.insert(callbacks, fn)
			return connection(callbacks, fn)
		end,
		Destroy = function(self)
			table.clear(callbacks)
			table.clear(self)
			button:Destroy()
		end,
	}

	clickButton.Activated:Connect(function()
		for _, v in callbacks do
			v()
		end
	end)

	return item
end

function PluginUI.createAttributeBox(list: ItemList?, attributeName: string?, attributeValue: any?, callback: ((string) -> ())?, nameLabelIsBox: boolean?, equalsVisible: boolean?)
	local equalsVisible = if equalsVisible == nil then true else false
	local attrBox = Instance.new("Frame")
	attrBox.Name = "AttributeBoxItem"
	attrBox.BackgroundTransparency = 1

	attrBox.Size = UDim2.new(1, 0, 0, 20)
	attrBox.BorderSizePixel = 0

	-- Attribute name label
	local nameLabel = Instance.new(nameLabelIsBox and "TextBox" or "TextLabel")
	nameLabel.Name = "NameLabel"
	nameLabel.BorderSizePixel = 0
	nameLabel:AddTag("JEXP_Text")

	nameLabel.Size = UDim2.new(0, 150, 1 - 0.1, 0)
	nameLabel.AnchorPoint = Vector2.new(0, 0.5)
	nameLabel.Position = UDim2.new(0 + 0.05, 0, 0.5, 0)
	nameLabel.BackgroundTransparency = 1
	nameLabel.Text = attributeName or "Attribute Name"
	nameLabel.TextXAlignment = Enum.TextXAlignment.Left
	nameLabel.Font = Enum.Font.SourceSans
	nameLabel.AutomaticSize = Enum.AutomaticSize.X
	nameLabel.TextSize = 12
	nameLabel.ZIndex = 4
	nameLabel.Parent = attrBox

	--borderFrame(UDim2.fromScale(0, 0), UDim2.fromScale(0.5, 1), attrBox)

	-- Value input box
	local valueBox = Instance.new("TextBox")
	valueBox.Name = "ValueBox"
	valueBox:AddTag("JEXP_Text")

	valueBox.BorderSizePixel = 0
	valueBox.TextTruncate = Enum.TextTruncate.AtEnd
	valueBox.ClearTextOnFocus = false
	valueBox.AnchorPoint = Vector2.new(0, 0.5)
	valueBox.Text = tostring(attributeValue or "")
	valueBox.TextXAlignment = Enum.TextXAlignment.Left
	valueBox.Font = Enum.Font.SourceSans
	valueBox.BackgroundTransparency = 1
	valueBox.TextSize = 12
	valueBox.PlaceholderText = "Enter value..."
	valueBox.PlaceholderColor3 = Color3.fromRGB(128, 128, 128)
	valueBox.ZIndex = 4
	valueBox.Parent = attrBox

	-- Equals label
	local equalsLabel = Instance.new("TextLabel")
	equalsLabel.Name = "EqualsLabel"
	equalsLabel:AddTag("JEXP_Text")
	equalsLabel.BorderSizePixel = 0
	equalsLabel.BackgroundTransparency = 1
	equalsLabel.Text = "="
	equalsLabel.Font = Enum.Font.SourceSans
	equalsLabel.TextSize = 12
	equalsLabel.AnchorPoint = Vector2.new(0, 0.5)
	equalsLabel.Position = UDim2.new(0, 0, 0.5, 0) -- updated in updateValueBox
	equalsLabel.Size = UDim2.new(0, 16, 1 - 0.1, 0)
	equalsLabel.ZIndex = 4
	equalsLabel.Visible = equalsVisible
	equalsLabel.Parent = attrBox

	local function updateValueBox()
		local nameLabelLocalX = nameLabel.AbsolutePosition.X - attrBox.AbsolutePosition.X
		local textRightEdge = nameLabelLocalX + nameLabel.TextBounds.X + PADDING
		local fixedEdge = nameLabelLocalX + FIXED_DISTANCE + PADDING

		local equalsX = math.max(fixedEdge, textRightEdge)
		local valueX = equalsX + EQUALS_WIDTH

		equalsLabel.Position = UDim2.new(0, equalsX, 0.5, 0)
		valueBox.Position = UDim2.new(0, valueX, 0.5, 0)
		valueBox.Size = UDim2.new(1, -valueX, 1 - 0.1, 0)

		local bindex = 0
		for _, v in valueBox:GetChildren() do
			if v.Name == "SideButton" then
				bindex += 1
			end
		end
		if bindex == 0 then return end

		local pos = valueBox.Position
		valueBox.Position = UDim2.new(
			pos.X.Scale,
			pos.X.Offset + 6 + (12 * bindex),
			pos.Y.Scale,
			pos.Y.Offset
		)
	end

	nameLabel:GetPropertyChangedSignal("TextBounds"):Connect(updateValueBox)
	nameLabel:GetPropertyChangedSignal("AbsolutePosition"):Connect(updateValueBox)
	attrBox:GetPropertyChangedSignal("AbsoluteSize"):Connect(updateValueBox)
	task.defer(updateValueBox)

	--borderFrame(UDim2.fromScale(0.5, 0), UDim2.fromScale(0.5, 1), valueBox)

	if list then
		list:AddItem(attrBox)
	end

	local iseditable = true
	local callbacks: {any} = { callback}
	local item: ValueUIItem<(v: string) -> ()> = {
		Instance = attrBox :: any,
		Get = function(self)
			return valueBox.Text
		end,
		Set = function(self, v)
			valueBox.Text = v
		end,
		SetText = function(self, new)
			valueBox.Text = new
		end,
		OnChange = function(self, fn)
			table.insert(callbacks, fn)
			return connection(callbacks, fn)
		end,
		Destroy = function(self)
			table.clear(callbacks)
			table.clear(self)
			attrBox:Destroy()
		end,
		SetEditable = function(self, editable)
			iseditable = editable
			if not editable then
				nameLabel:RemoveTag("JEXP_Text")
				nameLabel:AddTag("JEXP_FadedText")
				valueBox:RemoveTag("JEXP_Text")
				valueBox:AddTag("JEXP_FadedText")
				valueBox.TextEditable = false
				return
			end
			nameLabel:AddTag("JEXP_Text")
			nameLabel:RemoveTag("JEXP_FadedText")
			valueBox:AddTag("JEXP_Text")
			valueBox:RemoveTag("JEXP_FadedText")
			valueBox.TextEditable = true
		end,
		GetName = function(self)
			return nameLabel.Text
		end,
		SetName = function(self, new)
			nameLabel.Text = new
		end,
	}

	local lastValue = attributeValue
	valueBox.Focused:Connect(function()
		lastValue = valueBox.Text
	end)
	valueBox.FocusLost:Connect(function(enterPressed)
		--if not enterPressed then
		--	if lastValue then
		--		valueBox.Text = lastValue
		--	end
		--	return
		--end
		if not iseditable then return end
		for _, v in callbacks do
			v(valueBox.Text)
		end
	end)

	return item
end

function PluginUI.createSeparator(list: ItemList): Frame
	local separator = Instance.new("Frame")
	separator.Name = "Separator"
	separator:AddTag("JEXP_LightBackground")

	separator.Size = UDim2.new(1, 0, 0, 1)
	separator.BorderSizePixel = 0

	list:AddItem(separator)
	return separator
end

function PluginUI.createHeader(list: ItemList?, text: string?): Frame
	local header = Instance.new("Frame")
	header.Name = "HeaderItem"
	header:AddTag("JEXP_DarkBackground")

	header.Size = UDim2.new(1, 0, 0, 25)
	header.BorderSizePixel = 0

	local label = Instance.new("TextLabel")
	label.Name = "Label"
	label:AddTag("JEXP_Text")

	label.Size = UDim2.new(1, -16, 1, 0)
	label.Position = UDim2.new(0, 8, 0, 0)
	label.BackgroundTransparency = 1
	label.Text = text or "Header"
	label.TextXAlignment = Enum.TextXAlignment.Left
	label.Font = Enum.Font.SourceSansBold
	label.TextSize = 12
	label.Parent = header

	if list then
		list:AddItem(header)
	end
	return header
end

local visibleEnumLists: {EnumBox} = {}
type EnumBox = {
	ReconstructEnumList: (self: any, enumType: Enum, selectedValue: EnumItem?) -> (),
	IsOpen: boolean,
	List: ItemList
} & ValueUIItem

function PluginUI.clearOpenEnumLists()
	for _, v in visibleEnumLists do
		v.IsOpen = false
		v.List:Hide()
	end
	table.clear(visibleEnumLists)
end

function PluginUI.createEnumBox(
	list: ItemList?,
	attributeName: string?,
	enumType: Enum?,
	selectedValue: any?,
	callback: ((EnumItem) -> ())?,
	listParent: Instance?,
	onList: ((list: Instance) -> ())?,
	onAnchor: ((pos: Vector2, size: Vector2) -> (Vector2, UDim2))?
)
	: (EnumBox, ItemList)
	local enumBox = Instance.new("Frame")
	enumBox.Name = "EnumBoxItem"
	enumBox.BackgroundTransparency = 1

	enumBox.Size = UDim2.new(1, 0, 0, 20)
	enumBox.BorderSizePixel = 0

	local nameLabel = Instance.new("TextLabel")
	nameLabel.Name = "NameLabel"
	nameLabel.BorderSizePixel = 0
	nameLabel:AddTag("JEXP_Text")

	nameLabel.Size = UDim2.new(0.5 - 0.1, 0, 1 - 0.1, 0)
	nameLabel.AnchorPoint = Vector2.new(0, 0.5)
	nameLabel.Position = UDim2.new(0 + 0.05, 0, 0.5, 0)
	nameLabel.BackgroundTransparency = 1
	nameLabel.Text = attributeName or "Enum Property"
	nameLabel.TextXAlignment = Enum.TextXAlignment.Left
	nameLabel.Font = Enum.Font.SourceSans
	nameLabel.TextSize = 12
	nameLabel.ZIndex = 4
	nameLabel.Parent = enumBox

	local valueButton = Instance.new("TextButton")
	valueButton.Name = "ValueButton"
	valueButton:AddTag("JEXP_LightBackground")
	valueButton:AddTag("JEXP_Text")
	valueButton:AddTag("JEXP_Hover")

	valueButton.BorderSizePixel = 0
	valueButton.TextTruncate = Enum.TextTruncate.AtEnd
	valueButton.AnchorPoint = Vector2.new(1, 0.5)
	valueButton.Size = UDim2.new(0.5 - 0.1, 0, 1 - 0.1, 0)
	valueButton.Position = UDim2.new(1 - 0.05, 0, 0.5, 0)
	valueButton.Text = selectedValue and selectedValue.Name or ""
	valueButton.TextXAlignment = Enum.TextXAlignment.Left
	valueButton.Font = Enum.Font.SourceSans
	valueButton.TextSize = 12
	valueButton.ZIndex = 4
	valueButton.Parent = enumBox

	local arrow = Instance.new("ImageLabel")
	arrow.Name = "Arrow"
	arrow:AddTag("JEXP_Arrow")
	arrow.Size = UDim2.new(0, 12, 0, 12)
	arrow.Position = UDim2.new(1, -16, 0.5, -6)
	arrow.BackgroundTransparency = 1
	arrow.Parent = valueButton

	local dropdownList = PluginUI.createItemList({
		Size = UDim2.new(0.5 - 0.1, 0, 0, 150),
		ZIndex = 2000,
		Parent = enumBox
	})

	if onList then
		onList(dropdownList.Backdrop)
	end

	local currentValue = selectedValue
	local callbacks: {any} = {callback}

	local enumButtons: {TriggerUIItem} = {}

	local item: EnumBox = {
		List = dropdownList,
		IsOpen = false,
		Instance = enumBox :: any,
		Get = function(self)
			return currentValue
		end,
		Set = function(self, v)
			currentValue = v
			valueButton.Text = tostring(v)
		end,
		SetText = function(self, v)
			valueButton.Text = v
		end,
		OnChange = function(self, fn)
			table.insert(callbacks, fn)
			return connection(callbacks, fn)
		end,
		Destroy = function(self: any)
			table.clear(callbacks)
			table.clear(self)
			dropdownList:Destroy()
			enumBox:Destroy()
		end,
		SetEditable = function(self, editable)
			if not editable then
				nameLabel:RemoveTag("JEXP_Text")
				nameLabel:AddTag("JEXP_FadedText")
				valueButton:RemoveTag("JEXP_Text")
				valueButton:AddTag("JEXP_FadedText")
				valueButton:RemoveTag("JEXP_LightBackground")
				valueButton:AddTag("JEXP_DarkBackground")
				valueButton.Interactable = false
				valueButton.Active = false
				return
			end
			nameLabel:AddTag("JEXP_Text")
			nameLabel:RemoveTag("JEXP_FadedText")
			valueButton:AddTag("JEXP_Text")
			valueButton:RemoveTag("JEXP_FadedText")
			valueButton:AddTag("JEXP_LightBackground")
			valueButton:RemoveTag("JEXP_DarkBackground")
			valueButton.Interactable = true
			valueButton.Active = true
		end,
		GetName = function(self)
			return nameLabel.Text
		end,
		SetName = function(self, new)
			nameLabel.Text = new
		end,
		ReconstructEnumList = function(self: EnumBox, enumType: Enum, selectedValue: EnumItem?)
			local enumValues = {}
			for _, v in enumButtons do
				v:Destroy()
			end
			table.clear(enumButtons)
			if selectedValue then
				valueButton.Text = selectedValue.Name
			end
			for _, item in enumType:GetEnumItems() do
				table.insert(enumValues, item)
			end

			table.sort(enumValues, function(a, b) return a.Name < b.Name end)
			for _, enum: EnumItem in enumValues do
				local enumName = enum.Name
				local enumButton = PluginUI.createButton(dropdownList, enumName, function()
					valueButton.Text = enumName
					currentValue = enumName
					dropdownList:Hide()
					self.IsOpen = false

					for _, fn in callbacks do
						fn(enum)
					end
				end)
				table.insert(enumButtons, enumButton)
			end
		end,
	}

	if enumType then
		local enumValues = {}
		for _, item in enumType:GetEnumItems() do
			table.insert(enumValues, item)
		end

		table.sort(enumValues, function(a, b) return a.Name < b.Name end)
		for _, enum: EnumItem in enumValues do
			local enumName = enum.Name
			local enumButton = PluginUI.createButton(dropdownList, enumName, function()
				valueButton.Text = enumName
				currentValue = enumName
				dropdownList:Hide()
				item.IsOpen = false

				for _, fn in callbacks do
					fn(enum)
				end
			end)
			table.insert(enumButtons, enumButton)
		end
	end

	if list then
		list:AddItem(enumBox)
	end

	valueButton.Activated:Connect(function()
		if item.IsOpen then
			dropdownList:Hide()
			item.IsOpen = false
		else
			PluginUI.clearOpenEnumLists()

			local absolutePos = valueButton.AbsolutePosition
			local absoluteSize = valueButton.AbsoluteSize

			if onAnchor then
				dropdownList:SetAnchor(onAnchor(absolutePos, absoluteSize))
			else
				dropdownList:SetAnchor(Vector2.new(1, 0), UDim2.fromOffset(absolutePos.X, absolutePos.Y))
			end
			dropdownList:Show()
			table.insert(visibleEnumLists, item)

			item.IsOpen = true
		end
	end)

	return item::any, dropdownList
end

function PluginUI.createDropdownBox(list: ItemList?, attributeName: string?, attributeValue: any?, callback: ((string) -> ())?, dropdownItems: {(parentItem: ValueUIItem) -> ValueUIItem}?): (ValueUIItem, {ValueUIItem})
	local dropdownBox = Instance.new("Frame")
	dropdownBox.Name = "DropdownBoxItem"
	dropdownBox.BackgroundTransparency = 1

	dropdownBox.Size = UDim2.new(1, 0, 0, 20)
	--dropdownBox.ClipsDescendants = false
	dropdownBox.AutomaticSize = Enum.AutomaticSize.Y
	dropdownBox.BorderSizePixel = 0

	local absSize = dropdownBox.AbsoluteSize

	local expandArrow = Instance.new("ImageButton")
	expandArrow.Name = "ExpandArrow"
	expandArrow:AddTag("JEXP_Arrow")

	expandArrow.Size = UDim2.new(0, 12, 0, 12)
	expandArrow.Position = UDim2.new(0, 8, 0, (0.5 * absSize.Y) - 6)
	expandArrow.BackgroundTransparency = 1
	expandArrow.Rotation = -90
	expandArrow.Parent = dropdownBox

	local nameLabel = Instance.new("TextLabel")
	nameLabel.Name = "NameLabel"
	nameLabel.BorderSizePixel = 0
	nameLabel:AddTag("JEXP_Text")
	nameLabel:AddTag("JEXP_Hover")

	nameLabel.Size = UDim2.new(0.5 - 0.1, -20, 0, (1 - 0.1) * absSize.Y)
	nameLabel.AnchorPoint = Vector2.new(0, 0.5)
	nameLabel.Position = UDim2.new(0 + 0.05, 20, 0, 0.5 * absSize.Y)
	nameLabel.BackgroundTransparency = 1
	nameLabel.Text = attributeName or "Dropdown Property"
	nameLabel.TextXAlignment = Enum.TextXAlignment.Left
	nameLabel.Font = Enum.Font.SourceSans
	nameLabel.TextSize = 12
	nameLabel.ZIndex = 4
	nameLabel.Parent = dropdownBox

	local valueBox = Instance.new("TextBox")
	valueBox.Name = "ValueBox"
	valueBox:AddTag("JEXP_Text")

	valueBox.BorderSizePixel = 0
	valueBox.TextTruncate = Enum.TextTruncate.AtEnd
	valueBox.ClearTextOnFocus = false
	valueBox.AnchorPoint = Vector2.new(1, 0.5)
	valueBox.Size = UDim2.new(0.5 - 0.1, 0, 0, (1 - 0.1) * absSize.Y)
	valueBox.Position = UDim2.new(1 - 0.05, 0, 0, 0.5 * absSize.Y)
	valueBox.Text = attributeValue or ""
	valueBox.TextXAlignment = Enum.TextXAlignment.Left
	valueBox.Font = Enum.Font.SourceSans
	valueBox.BackgroundTransparency = 1
	valueBox.TextSize = 12
	valueBox.PlaceholderText = "Enter value..."
	valueBox.PlaceholderColor3 = Color3.fromRGB(128, 128, 128)
	valueBox.ZIndex = 4
	valueBox.Parent = dropdownBox

	local dropdownList = PluginUI.createItemList({
		AutomaticSize = Enum.AutomaticSize.XY,
		Position = UDim2.new(0, 0, 0, absSize.Y + 2),
		ZIndex = 2000,
		BackgroundTransparency = 0,
		--Visible = true,
		Parent = dropdownBox
	})
	dropdownList.Backdrop:RemoveTag("JEXP_Border")
	dropdownList.Backdrop.AutomaticSize = Enum.AutomaticSize.Y
	dropdownList.Backdrop.Size = UDim2.new(1, 0, 0, 0)
	dropdownList.Container.Size = UDim2.new(1, 0, 0, 0)
	dropdownList.Container.AutomaticSize = Enum.AutomaticSize.Y
	dropdownList.Container.AutomaticCanvasSize = Enum.AutomaticSize.Y
	dropdownList.Container.Position = UDim2.new(0, 20, 0, 0)
	if dropdownItems then
		dropdownList.Container.CanvasSize = UDim2.new(1, 0, 0, (#dropdownItems + 0) * absSize.Y)
	end

	local callbacks: {any} = {callback}
	local items: {ValueUIItem} = {}
	local isExpanded = false

	if list then
		list:AddItem(dropdownBox)
	end

	local iseditable = false

	local item: ValueUIItem<(v: string) -> ()> = {
		Instance = dropdownBox :: any,
		Get = function(self)
			return valueBox.Text
		end,
		Set = function(self, v)
			valueBox.Text = v
		end,
		SetText = function(self, new)
			valueBox.Text = new
		end,
		OnChange = function(self, fn)
			table.insert(callbacks, fn)
			return connection(callbacks, fn)
		end,
		Destroy = function(self)
			table.clear(callbacks)
			table.clear(self)
			dropdownList:Destroy()
			dropdownBox:Destroy()
		end,
		SetEditable = function(self, editable)
			iseditable = editable
			for _, v in items do
				v:SetEditable(editable)
			end

			if not editable then
				nameLabel:RemoveTag("JEXP_Text")
				nameLabel:AddTag("JEXP_FadedText")
				valueBox:RemoveTag("JEXP_Text")
				valueBox:AddTag("JEXP_FadedText")
				valueBox.TextEditable = false
				nameLabel.Interactable = false
				nameLabel.Active = false
				return
			end
			nameLabel:AddTag("JEXP_Text")
			nameLabel:RemoveTag("JEXP_FadedText")
			valueBox:AddTag("JEXP_Text")
			valueBox:RemoveTag("JEXP_FadedText")
			valueBox.TextEditable = true
			nameLabel.Interactable = true
			nameLabel.Active = true
		end,
		GetName = function(self)
			return nameLabel.Text
		end,
		SetName = function(self, new)
			nameLabel.Text = new
		end,
	}

	if dropdownItems then
		for _, createChildItem in dropdownItems do
			local childItem = createChildItem(item)
			dropdownList:AddItem(childItem.Instance)
			table.insert(items, childItem)
		end
	end

	local function toggleDropdown()
		if isExpanded then
			dropdownList:Hide()
			expandArrow.Rotation = -90
			isExpanded = false
		else
			dropdownList:Show()
			expandArrow.Rotation = 0
			isExpanded = true
		end
	end

	expandArrow.Activated:Connect(toggleDropdown)
	local lastValue = attributeValue
	valueBox.Focused:Connect(function()
		lastValue = valueBox.Text
	end)
	valueBox.FocusLost:Connect(function(enterPressed)
		--if not enterPressed then
		--	if lastValue then
		--		valueBox.Text = lastValue
		--	end
		--	return
		--end
		if not iseditable then return end
		for _, v in callbacks do
			v(valueBox.Text)
		end
	end)

	return item, items
end

function PluginUI.createInsertButton(parent: Instance?, fn: () -> ())
	local insertButton = Instance.new("TextButton")
	insertButton:AddTag("JEXP_Text")
	insertButton:AddTag("JEXP_LightBackground")

	insertButton.BorderSizePixel = 0
	insertButton.AnchorPoint = Vector2.new(1, 0.5)
	insertButton.Position = UDim2.fromScale(0.9, 0.5)
	insertButton.Size = UDim2.fromOffset(20, 20)
	insertButton.TextScaled = true
	insertButton.Text = "+"
	insertButton.BackgroundTransparency = 1
	insertButton.Activated:Connect(fn)

	insertButton.Parent = parent
	return insertButton
end

function PluginUI.createInsertButtonWithImage(parent: Instance?, image: string, fn: () -> ())
	local insertButton = Instance.new("ImageButton")
	insertButton:AddTag("JEXP_LightBackground")

	insertButton.BorderSizePixel = 0
	insertButton.AnchorPoint = Vector2.new(1, 0.5)
	insertButton.Position = UDim2.fromScale(0.9, 0.5)
	insertButton.Size = UDim2.fromOffset(20, 20)
	insertButton.Image = image
	insertButton.BackgroundTransparency = 1
	insertButton.Activated:Connect(fn)

	insertButton.Parent = parent
	return insertButton
end

function PluginUI.createDeleteButton(parent: Instance?, fn: () -> ())
	local deleteButton = Instance.new("TextButton")
	deleteButton:AddTag("JEXP_Text")
	deleteButton:AddTag("JEXP_LightBackground")

	deleteButton.BorderSizePixel = 0
	deleteButton.AnchorPoint = Vector2.new(1, 0.5)
	deleteButton.Position = UDim2.fromScale(0.85, 0.5)
	deleteButton.Size = UDim2.fromOffset(20, 20)
	deleteButton.TextScaled = true
	deleteButton.Text = "-"
	deleteButton.BackgroundTransparency = 1
	deleteButton.Activated:Connect(fn)

	deleteButton.Parent = parent
	return deleteButton
end

function PluginUI.createSideButtonImage(image: string, imageRectOffset: Vector2, imageRectSize: Vector2, container: GuiObject, bindex: number?)
	local bindex = bindex or 0
	local sideButton = Instance.new("ImageButton")
	sideButton:AddTag("JEXP_LightBackground")

	sideButton.Name = "SideButton"
	sideButton.BorderSizePixel = 0
	sideButton.AnchorPoint = Vector2.new(1, 0.5)
	sideButton.Position = UDim2.new(0, -(6 + (12 * bindex)), 0.5, 0)
	sideButton.Size = UDim2.fromOffset(12, 12)
	sideButton.Image = image
	sideButton.ImageRectOffset = imageRectOffset
	sideButton.ImageRectSize = imageRectSize

	local pos = container.Position
	container.Position = UDim2.new(
		pos.X.Scale,
		pos.X.Offset + (sideButton.Size.X.Offset + (6 + (12 * bindex))),
		pos.Y.Scale,
		pos.Y.Offset
	)
	sideButton.Parent = container

	return sideButton
end

function PluginUI.createSideButtonText(text: string, container: GuiObject, bindex: number?)
	local bindex = bindex or 0
	local sideButton = Instance.new("TextButton")
	sideButton:AddTag("JEXP_LightBackground")

	sideButton.BorderSizePixel = 0
	sideButton.AnchorPoint = Vector2.new(1, 0.5)
	sideButton.Position = UDim2.new(0, -(6 + (12 * bindex)), 0.5, 0)
	sideButton.Size = UDim2.fromOffset(12, 12)
	sideButton.Text = text

	local pos = container.Position
	container.Position = UDim2.new(
		pos.X.Scale,
		pos.X.Offset + (sideButton.Size.X.Offset + (6 + (12 * bindex))),
		pos.Y.Scale,
		pos.Y.Offset
	)
	sideButton.Parent = container

	return sideButton
end

local point = Path2DControlPoint.new
local ZERO = UDim2.fromScale(0, 0)
function PluginUI.createLine(parent: Instance?, p1: UDim2, p2: UDim2, xoffset1: number, radius1: number, xoffset2: number, radius2: number, color: Color3)
	local path = Instance.new("Path2D")
	path:SetControlPoints {
		point(p1, ZERO, ZERO),
		point(
			UDim2.new(0, p1.X.Offset + xoffset1, 0, p1.Y.Offset + radius1),
			UDim2.new(0, 0, 0, -radius1),
			UDim2.new(0, 0, 0, radius1)
		),
		point(
			UDim2.new(0, p2.X.Offset + xoffset2, 0, p2.Y.Offset - radius2),
			UDim2.new(0, 0, 0, -radius2),
			UDim2.new(0, 0, 0, radius2)
		),
		point(p2, ZERO, ZERO)
	}

	path.Parent = parent
	path.Color3 = color
	return path
end

function PluginUI.createCancelCreateButtons()
	local container = Instance.new("Frame")
	container.Name = "CreateConfirmContainer"
	container.BackgroundTransparency = 1

	container.Size = UDim2.new(1, 0, 0, 20)
	container.BorderSizePixel = 0

	local cancelButton: TriggerUIItem do
		local button = Instance.new("TextButton")
		button.Name = "button"
		button:AddTag("JEXP_LightBackground")
		button:AddTag("JEXP_Text")
		button:AddTag("JEXP_Hover")

		button.BorderSizePixel = 0
		button.TextTruncate = Enum.TextTruncate.AtEnd
		button.AnchorPoint = Vector2.new(1, 0.5)
		button.Size = UDim2.new(0.5 - 0.1, 0, 1 - 0.1, 0)
		button.Position = UDim2.new(1 - 0.05, 0, 0.5, 0)
		button.Text = "Cancel"
		button.TextXAlignment = Enum.TextXAlignment.Center
		button.Font = Enum.Font.SourceSans
		button.TextSize = 12
		button.ZIndex = 4
		button.Parent = container

		local callbacks = {}
		cancelButton = {
			Instance = button,
			OnTrigger = function(self, callback)
				table.insert(callbacks, callback)
				return connection(callbacks, callback)
			end,
			Trigger = function(self)
				for _, v in callbacks do
					v()
				end
			end,
			Destroy = function(self)
				button:Destroy()
			end,
		}
		button.Activated:Connect(function()
			for _, v in callbacks do
				v()
			end
		end)
	end

	local confirmButton: TriggerUIItem do
		local button = Instance.new("TextButton")
		button.Name = "button"
		button:AddTag("JEXP_SelectedColor")

		button.BorderSizePixel = 0
		button.TextTruncate = Enum.TextTruncate.AtEnd
		button.AnchorPoint = Vector2.new(0, 0.5)
		button.Size = UDim2.new(0.5 - 0.1, 0, 1 - 0.1, 0)
		button.Position = UDim2.new(0.05, 0, 0.5, 0)
		button.Text = "Confirm"
		button.TextXAlignment = Enum.TextXAlignment.Center
		button.Font = Enum.Font.SourceSans
		button.TextSize = 12
		button.ZIndex = 4
		button.Parent = container

		local callbacks = {}
		confirmButton = {
			Instance = button,
			OnTrigger = function(self, callback)
				table.insert(callbacks, callback)
				return connection(callbacks, callback)
			end,
			Trigger = function(self)
				for _, v in callbacks do
					v()
				end
			end,
			Destroy = function(self)
				button:Destroy()
			end,
		}
		button.Activated:Connect(function()
			for _, v in callbacks do
				v()
			end
		end)
	end

	return container, cancelButton, confirmButton
end

function PluginUI.createCreatorWidget(title: string, items: {ValueUIItem})
	local widget = plugin:CreateDockWidgetPluginGuiAsync(
		PluginUI.getId("Creator", true), DockWidgetPluginGuiInfo.new(
			Enum.InitialDockState.Float,
			true,
			true,
			200,
			100
		)
	)
	widget:AddTag("JEXP_Widget")
	local UITemplates = require("./UITemplates")
	local editorFrame = UITemplates.EditorFrame()
	editorFrame.Parent = widget

	widget.Name = "CREATORWIDGET"
	widget.Title = title
	widget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	widget:BindToClose(function()
		widget.Enabled = not widget.Enabled
	end)

	editorFrame:AddTag("JEXP_Background")
	editorFrame.Parent = widget

	editorFrame.Items.Content:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
		local size = editorFrame.Items.Content.AbsoluteSize
		editorFrame.Items.Overlay.Size = UDim2.fromOffset(size.X, size.Y)
	end)

	local list = PluginUI.createItemListFromExisting(editorFrame, editorFrame.Items.Content, editorFrame.Items)
	for _, v in items do
		list:AddItem(v.Instance)
	end

	local container, cancel, confirm = PluginUI.createCancelCreateButtons()
	list:AddItem(container)
	widget:BindToClose(function()
		widget.Enabled = false
		widget:Destroy()
	end)

	return widget, cancel, confirm, function()
		widget.Enabled = false
		task.wait(0.1)
		widget:Destroy()
	end
end

function PluginUI.createPopupWidget(title: string, items: {ValueUIItem})
	local widget = plugin:CreateDockWidgetPluginGuiAsync(
		PluginUI.getId("Popup", true), DockWidgetPluginGuiInfo.new(
			Enum.InitialDockState.Float,
			true,
			true,
			200,
			100
		)
	)
	widget:AddTag("JEXP_Widget")
	local UITemplates = require("./UITemplates")
	local editorFrame = UITemplates.EditorFrame()
	editorFrame.Parent = widget

	widget.Name = "POPUPWIDGET"
	widget.Title = title
	widget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	widget:BindToClose(function()
		widget.Enabled = false
		widget:Destroy()
	end)

	editorFrame:AddTag("JEXP_Background")
	editorFrame.Parent = widget

	editorFrame.Items.Content:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
		local size = editorFrame.Items.Content.AbsoluteSize
		editorFrame.Items.Overlay.Size = UDim2.fromOffset(size.X, size.Y)
	end)

	local list = PluginUI.createItemListFromExisting(editorFrame, editorFrame.Items.Content, editorFrame.Items)
	for _, v in items do
		list:AddItem(v.Instance)
	end

	return widget, function()
		widget.Enabled = false
		task.wait(0.1)
		widget:Destroy()
	end
end

return PluginUI