--#selene: allow(shadowing)
-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent.Parent
local plugin = root:FindFirstAncestorWhichIsA("Plugin")
local mouse = plugin:GetMouse()
local ChangeHistoryService = game:GetService("ChangeHistoryService")

local Fusion = require(root.Packages.fusion)
local RedonUI = require(root.Packages["redon-ui"])
local types = require(root.Plugin.types)
local vehicle = require(root.Plugin.vehicle)
local chassisHandler = require(root.Plugin.chassisHandler)
local log = require(root.Plugin.log)

local billboards = require(script.billboards)
local main = require(script.main)
local components = {
	billboards = billboards,
	main = main,
}

type props = {
	Name: string,
	Version: string,

	Enabled: Fusion.Value<boolean>,
	Mode: Fusion.Value<types.mode>,
	CurrentVehicle: Fusion.Value<Model | string?>,
	ReturnTo: Fusion.Value<types.returnTo>,
}

--[[
	Viewport
	Creates the viewport for vehicle customization and placement
]]
return function(baseScope: types.Scope, props: props)
	local scope = Fusion.innerScope(baseScope, components)
	local currentTheme = RedonUI.theme.theme:now()

	-- Placing Vehicle Handling
	local placingVehicle: Fusion.Value<vehicle.vehicleFolder?> = scope:Value(nil)
	local heightOffset = 0

	local worldModel = scope:New("WorldModel") {
		[Fusion.Children] = {
			scope:Computed(function(use)
				local currentlyPlacing = use(placingVehicle)
				if currentlyPlacing ~= nil then
					local veh = currentlyPlacing.vehicle:Clone()
					veh.PrimaryPart = veh:FindFirstChildWhichIsA("VehicleSeat")
					return scope:Hydrate(veh) {} :: Instance?
				end
				return nil
			end),
		},
	} :: WorldModel

	-- Customizing Vehicle Handling
	local currentVehicleFolder: Fusion.Value<vehicle.vehicleFolder?> = scope:Value(nil)
	local basePoint: Instance? = nil
	local outfitting: Fusion.Value<Model?> = scope:Value(nil)
	local attachments: Fusion.Value<{ Attachment }> = scope:Value({})
	local lightParts: Fusion.Value<{ { name: string, part: BasePart } }> = scope:Value({})
	local connections: { RBXScriptConnection } = {}
	local currentLocation: Fusion.Value<string?> = scope:Value(nil)
	local currentEquipment = scope:Value("")
	local currentlyConfiguring: Fusion.Value<string?> = scope:Value(nil)
	local currentOptionData: Fusion.Value<{ [string]: boolean | { [string]: boolean } }> = scope:Value({})

	local function cleanupCustomization()
		currentVehicleFolder:set(nil)
		if basePoint ~= nil then
			basePoint:Destroy()
			basePoint = nil
		end
		outfitting:set(nil)
		attachments:set({})
		lightParts:set({})
		currentLocation:set(nil)
		currentlyConfiguring:set(nil)
		for i, connection in pairs(connections) do
			connection:Disconnect()
			connections[i] = nil
		end
		plugin:Deactivate() -- Return the user to the default selection tool
		log.debug("Cleaned up customization")
	end
	table.insert(scope, cleanupCustomization)

	scope:Observer(currentLocation):onChange(function()
		local location = scope.peek(currentLocation)
		local outfittingModel = scope.peek(outfitting)
		if location == nil or outfittingModel == nil then
			currentEquipment:set("")
		else
			local newState = outfittingModel:GetAttribute(location)
			currentEquipment:set(if typeof(newState) == "string" then newState else "")
		end
	end)

	scope:Observer(currentlyConfiguring):onChange(function()
		local vehicleFolder = scope.peek(currentVehicleFolder)
		local outfittingModel = scope.peek(outfitting)
		local location = scope.peek(currentLocation)
		local equipment = scope.peek(currentEquipment)
		local configuring = scope.peek(currentlyConfiguring)
		if
			equipment == ""
			or location == nil
			or configuring == nil
			or outfittingModel == nil
			or vehicleFolder == nil
		then
			currentOptionData:set({})
		else
			local equipmentModel = outfittingModel:FindFirstChild(equipment)
			if equipmentModel == nil then
				currentOptionData:set({})
				return
			end

			local data: { [string]: boolean | { [string]: boolean } } = {}
			for configurationName, configurationData in
				pairs(vehicleFolder.data.customization[location][equipment].configuration)
			do
				local value = equipmentModel:GetAttribute(configurationName)
				if configurationData.type == "single" then
					if value == nil or typeof(value) ~= "boolean" then
						value = false
					end
					data[configurationName] = value :: boolean
				elseif configurationData.type == "select" and configurationData.choices ~= nil then
					local enabled = {}
					if value ~= nil and typeof(value) == "string" then
						enabled = value:split("+")
					end

					for _, optionName in pairs(configurationData.choices) do
						if data[configurationName] == nil then
							data[configurationName] = {}
						end
						if table.find(enabled, optionName) then
							(data[configurationName] :: { [string]: boolean })[optionName] = true
						else
							(data[configurationName] :: { [string]: boolean })[optionName] = false
						end
					end
				end
			end
			currentOptionData:set(data)
		end
	end)

	scope:Observer(props.Enabled):onChange(function()
		if not scope.peek(props.Enabled) then
			cleanupCustomization()
			props.Mode:set("menu")
			props.CurrentVehicle:set(nil)
		end
	end)

	-- Shared
	local function recomputeLightParts()
		local currentVehicle = scope.peek(props.CurrentVehicle)
		if currentVehicle == nil or typeof(currentVehicle) ~= "Instance" then
			lightParts:set({})
			return
		end

		local carOutfitting = vehicle:getOutfitting(currentVehicle)
		if carOutfitting == nil then
			lightParts:set({})
			return
		end

		local newLightParts = {}
		for _, lightPart in pairs(carOutfitting.model:GetDescendants()) do
			if lightPart:IsA("BasePart") and lightPart.Parent ~= nil and lightPart.Parent.Name == "_LIGHTS" then
				table.insert(newLightParts, { name = lightPart.Name, part = lightPart })
			end
		end
		log.debug("Recomputed light parts", newLightParts)
		lightParts:set(newLightParts)
	end

	local function modeChange()
		local currentVehicle = scope.peek(props.CurrentVehicle)
		if scope.peek(props.Mode) == "viewport" and typeof(currentVehicle) == "string" then
			local vehicleFolder = vehicle:getVehicle(currentVehicle)
			if vehicleFolder ~= nil then
				placingVehicle:set(vehicleFolder)
				local vehicleCFrame, vehicleSize = vehicleFolder.vehicle:GetBoundingBox()
				heightOffset = vehicleSize.Y / 2
				worldModel.WorldPivot = vehicleCFrame
			end
		elseif scope.peek(props.Mode) == "viewport" and currentVehicle ~= nil then
			local vehicleName = currentVehicle:GetAttribute("EVCVehicleName")
			if vehicleName ~= nil and typeof(vehicleName) == "string" and vehicle.vehicles[vehicleName] ~= nil then
				local vehicleFolder = vehicle:getVehicle(vehicleName)
				if vehicleFolder == nil then
					log.warn("Failed to open vehicle customization, no vehicle found", vehicleName)
					return
				end
				local newBasePoint = vehicle:cloneCustomizationBasePoint(vehicleFolder)
				if newBasePoint == nil then
					log.warn("Failed to open vehicle customization, no basepoint found", vehicleName)
					return
				end
				local carOutfitting = vehicle:getOutfitting(currentVehicle)
				if carOutfitting == nil then
					return
				end

				basePoint = newBasePoint
				outfitting:set(carOutfitting.model)

				local newAttachments = {}
				for _, attachment in pairs(newBasePoint:GetChildren()) do
					if attachment:IsA("Attachment") then
						table.insert(newAttachments, attachment)
					end
				end
				attachments:set(newAttachments)

				newBasePoint.CFrame = carOutfitting.basePoint.CFrame
				table.insert(
					connections,
					carOutfitting.basePoint:GetPropertyChangedSignal("CFrame"):Connect(function()
						newBasePoint.CFrame = carOutfitting.basePoint.CFrame
					end)
				)
				table.insert(
					connections,
					currentVehicle:GetPropertyChangedSignal("Parent"):Connect(function()
						if currentVehicle:IsDescendantOf(workspace) == false then
							cleanupCustomization()
							props.CurrentVehicle:set(nil)
							props.Mode:set("menu")
						end
					end)
				)

				-- We want to update the value last
				-- to ensure proper cascading of changes
				currentVehicleFolder:set(vehicleFolder)
				recomputeLightParts()
			end
		elseif scope.peek(props.Mode) == "viewport" then
			props.Mode:set("menu")
			log.warn("Got sent to viewport mode without a vehicle")
		end
	end
	scope:Observer(props.Mode):onChange(modeChange)
	scope:Observer(props.CurrentVehicle):onChange(modeChange)

	local viewportSize = scope:Value(Vector2.one)
	return scope:New("ScreenGui") {
		Name = "EVC",
		Enabled = scope:Computed(function(use)
			return use(props.Enabled) and use(props.Mode) == "viewport"
		end),
		DisplayOrder = 100,
		ResetOnSpawn = false,
		[Fusion.Out "AbsoluteSize"] = viewportSize,

		[Fusion.Children] = Fusion.Child {
			scope:textLabel {
				Size = UDim2.fromScale(1, 1),
				AnchorPoint = Vector2.new(0.5, 0.5),
				Position = UDim2.fromScale(0.5, 0.5),
				BackgroundColor3 = currentTheme.colors.mantle,
				BackgroundTransparency = 0.5,
				Text = "Please wait while the vehicle is loaded..",
				Visible = scope:Computed(function(use)
					return use(currentVehicleFolder) == nil and use(placingVehicle) == nil
				end),
			},

			scope:billboards {
				attachments = attachments,
				viewportSize = viewportSize,
				currentLocation = currentLocation,
				currentlyConfiguring = currentlyConfiguring,
				lightParts = lightParts,
			},

			scope:main {
				attachments = attachments,
				viewportSize = viewportSize,
				currentLocation = currentLocation,

				outfitting = outfitting,
				CurrentVehicle = props.CurrentVehicle,
				Mode = props.Mode,
				currentVehicleFolder = currentVehicleFolder,
				currentEquipment = currentEquipment,

				currentlyConfiguring = currentlyConfiguring,
				currentOptionData = currentOptionData,

				cleanupCustomization = cleanupCustomization,
				recomputeLightParts = recomputeLightParts,
				ReturnTo = props.ReturnTo,
			},

			scope:base {
				ClassName = "TextButton",
				Name = "Placement",
				AnchorPoint = Vector2.new(0.5, 0.5),
				Position = UDim2.fromScale(0.5, 0.5),
				Size = UDim2.fromScale(1, 1),
				BackgroundTransparency = 1,
				Text = "",
				Visible = scope:Computed(function(use)
					return use(placingVehicle) ~= nil
				end),

				[Fusion.OnEvent "MouseMoved"] = function()
					worldModel:MoveTo(mouse.Hit.Position + Vector3.new(0, heightOffset, 0))
				end :: unknown,

				[Fusion.OnEvent "Activated"] = function()
					local currentlyPlacing = scope.peek(placingVehicle)
					local currentVehicle = scope.peek(props.CurrentVehicle)
					if currentlyPlacing == nil or currentVehicle == nil then
						return
					end

					local newVehicle = currentlyPlacing.vehicle:Clone()
					newVehicle.Name = scope.peek(currentVehicle)
					newVehicle.Parent = workspace
					newVehicle:PivotTo(worldModel:GetPivot())
					vehicle:initVehicle(newVehicle)
					chassisHandler:installPlugin(newVehicle)
					props.CurrentVehicle:set(newVehicle)
					currentVehicleFolder:set(currentlyPlacing)
					placingVehicle:set(nil)
					ChangeHistoryService:SetWaypoint("Setup Vehicle")
				end,

				[Fusion.Children] = {
					scope:New("ViewportFrame") {
						Size = UDim2.fromScale(1, 1),
						Position = UDim2.fromScale(0.5, 0.5),
						AnchorPoint = Vector2.new(0.5, 0.5),
						BackgroundTransparency = 1,
						CurrentCamera = workspace.CurrentCamera,
						ImageTransparency = 0.5,

						[Fusion.Children] = {
							worldModel,
						},
					},

					scope:textButton {
						Name = "Cancel",
						AnchorPoint = Vector2.new(0.5, 0),
						Position = UDim2.fromScale(0.5, 0.0092592593),
						Size = UDim2.fromScale(0.078125, 0.0462962963),
						BackgroundColor3 = currentTheme.colors.crust,
						Border = 2,
						BorderColor3 = currentTheme.colors.text,
						CornerRadius = UDim.new(0.2, 0),
						Text = "Cancel",
						TextScaled = true,
						FontWeight = Enum.FontWeight.Bold,
						Reactive = true,
						MaxTextSize = 35,

						[Fusion.OnEvent "Activated"] = function()
							placingVehicle:set(nil)
							props.CurrentVehicle:set(nil)
							props.Mode:set("menu")
						end :: unknown,
					},
				},
			},

			scope:textLabel {
				Name = "EVCActive",
				AnchorPoint = Vector2.new(0, 1),
				Position = UDim2.new(0, 5, 1, -5),
				Size = UDim2.fromOffset(400, 18),
				BackgroundTransparency = 1,
				TextTransparency = 0.5,

				Text = "Emergency Vehicle Creator Active",
				TextSize = 15,
				TextXAlignment = Enum.TextXAlignment.Left,
			},

			scope:textLabel {
				Name = "Version",
				AnchorPoint = Vector2.new(1, 1),
				Position = UDim2.new(1, -5, 1, -5),
				Size = UDim2.fromOffset(400, 18),
				BackgroundTransparency = 1,
				TextTransparency = 0.5,

				Text = props.Version,
				TextSize = 15,
				TextXAlignment = Enum.TextXAlignment.Right,
			},
		},
	}
end


--#selene: allow(shadowing)
local root = script.Parent.Parent.Parent.Parent

local Fusion = require(root.Packages.fusion)
local RedonUI = require(root.Packages["redon-ui"])
local assets = require(root.Plugin.assets)
local types = require(root.Plugin.types)
local vehicle = require(root.Plugin.vehicle)
local log = require(root.Plugin.log)
local configuring = require(script.Parent.configuring)

local function construct(
	scope: types.Scope,
	currentVehicle: vehicle.vehicleFolder,
	outfitting: Model,
	props: { [string]: any }
)
	local currentTheme = RedonUI.theme.theme:now()
	return scope:ForPairs(
		currentVehicle.data.customization,
		function(_, scope: typeof(scope), key: string, value: { [string]: vehicle.equipmentData })
			return key,
				scope:base {
					Name = key,
					ClassName = "Frame",
					Size = UDim2.fromScale(1, 0),
					BackgroundTransparency = 1,
					AutomaticSize = Enum.AutomaticSize.Y,
					Visible = scope:Computed(function(use)
						return use(props.currentLocation) == key and use(props.currentlyConfiguring) == nil
					end),

					[Fusion.Children] = Fusion.Child {
						scope:New("UIListLayout") {
							SortOrder = Enum.SortOrder.LayoutOrder,
						},

						scope:ForPairs(
							value,
							function(_, scope: typeof(scope), name: string, data: vehicle.equipmentData)
								local isHovered = scope:Value(false)
								return data.layoutOrder,
									scope:base {
										ClassName = "Frame",
										Size = UDim2.fromOffset(300, 50),
										BackgroundColor3 = scope:Computed(function(_)
											return data.layoutOrder % 2 == 0 and currentTheme.colors.base
												or currentTheme.colors.mantle
										end),
										AutomaticSize = Enum.AutomaticSize.Y,
										ClipsDescendants = true,
										LayoutOrder = data.layoutOrder,

										[Fusion.Children] = {
											scope:base {
												ClassName = "ImageLabel",
												Image = data.icon,
												Size = UDim2.fromOffset(40, 40),
												AnchorPoint = Vector2.new(0, 0.5),
												Position = UDim2.new(0, 5, 0.5, 0),
												ScaleType = Enum.ScaleType.Crop,
												BackgroundTransparency = 1,
											},

											scope:textLabel {
												Size = UDim2.fromOffset(200, 50),
												Position = UDim2.fromOffset(50, 0),
												Text = name,
												TextSize = 25,
												FontWeight = scope:Computed(function(use)
													return if use(props.currentEquipment) == name
														then Enum.FontWeight.Bold
														else Enum.FontWeight.Regular
												end),
												TextWrapped = true,
												TextXAlignment = Enum.TextXAlignment.Left,
												AutomaticSize = Enum.AutomaticSize.Y,

												[Fusion.Children] = {
													scope:New("UIPadding") {
														PaddingTop = UDim.new(0, 10),
														PaddingBottom = UDim.new(0, 10),
													},
												},
											},

											scope:iconButton {
												ClassName = "ImageLabel",
												Size = UDim2.fromOffset(25, 25),
												Position = UDim2.new(1, -10, 0.5, 0),
												AnchorPoint = Vector2.new(1, 0.5),
												BackgroundTransparency = 1,
												Image = scope:Computed(function(use)
													return if use(isHovered)
														then assets.images.redonUI.createFilled
														else assets.images.redonUI.create
												end),
												ScaleType = Enum.ScaleType.Fit,
												Reactive = true,

												MouseEnter = function()
													isHovered:set(true)
												end,

												MouseLeave = function()
													isHovered:set(false)
												end,

												[Fusion.OnEvent "Activated"] = function()
													local currentLocation = scope.peek(props.currentLocation)
													if currentLocation == nil then
														return
													end
													if scope.peek(props.currentEquipment) ~= name then
														if
															vehicle:changeEquipment(
																currentVehicle,
																outfitting,
																currentLocation,
																name
															) == false
														then
															log.warn("failed to change equipment")
															return
														end
														props.recomputeLightParts()
														props.currentEquipment:set(name)
													end
													props.currentlyConfiguring:set(name)
												end,
											},
										},
									}
							end
						),
					},
				}
		end
	)
end

return function(
	baseScope: types.Scope,
	props: {
		attachments: Fusion.UsedAs<{ Attachment }>,
		viewportSize: Fusion.Value<Vector2>,
		currentLocation: Fusion.Value<string?>,

		Mode: Fusion.Value<types.mode>,
		CurrentVehicle: Fusion.Value<Model | string?>,

		outfitting: Fusion.Value<Model?>,
		currentVehicleFolder: Fusion.Value<vehicle.vehicleFolder?>,
		currentEquipment: Fusion.Value<string>,
		currentlyConfiguring: Fusion.Value<string?>,
		currentOptionData: Fusion.Value<{ [string]: boolean | { [string]: boolean } }>,

		cleanupCustomization: () -> (),
		recomputeLightParts: () -> (),
		ReturnTo: Fusion.Value<types.returnTo>,
	}
)
	local currentTheme = RedonUI.theme.theme:now()
	local scope = baseScope:innerScope({ configuring = configuring })

	return scope:base {
		Name = "Main",
		AnchorPoint = Vector2.new(0.5, 0.5),
		Position = UDim2.fromScale(0.5, 0.5),
		Size = UDim2.fromScale(1, 1),
		BackgroundTransparency = 1,
		Visible = scope:Computed(function(use)
			return typeof(use(props.CurrentVehicle)) == "Instance"
		end),

		[Fusion.Children] = {
			scope:base {
				ClassName = "ScrollingFrame",
				AnchorPoint = Vector2.new(0.5, 0),
				Position = UDim2.fromScale(0.5, 0.0092592593),
				Size = UDim2.fromScale(1, 0.0462962963),
				BackgroundTransparency = 1,
				CanvasSize = UDim2.new(),
				AutomaticCanvasSize = Enum.AutomaticSize.X,
				ScrollBarThickness = 2,
				HorizontalScrollBarInset = Enum.ScrollBarInset.None,
				ClipsDescendants = false,

				[Fusion.Children] = {
					scope:New("UIListLayout") {
						Padding = UDim.new(0.0052083333, 0),
						SortOrder = Enum.SortOrder.LayoutOrder,
						FillDirection = Enum.FillDirection.Horizontal,
						HorizontalAlignment = Enum.HorizontalAlignment.Center,
						VerticalAlignment = Enum.VerticalAlignment.Center,
					},

					scope:textButton {
						Name = "Cancel",
						Size = UDim2.new(0.078125, 0, 1, 0),
						BackgroundColor3 = currentTheme.colors.crust,
						Border = 2,
						BorderColor3 = currentTheme.colors.text,
						CornerRadius = UDim.new(0.2, 0),
						Text = "Go Back",
						TextScaled = true,
						FontWeight = Enum.FontWeight.Bold,
						Reactive = true,

						[Fusion.OnEvent "Activated"] = function()
							if scope.peek(props.currentVehicleFolder) == nil then
								log.warn("Please wait until the vehicle is fully loaded before using any inputs")
								return
							end
							props.cleanupCustomization()
							props.Mode:set("menu")
							props.CurrentVehicle:set(nil)
						end :: unknown,

						MaxTextSize = 35,
					},

					scope:textButton {
						Name = "Continue",
						Size = UDim2.new(0.078125, 0, 1, 0),
						BackgroundColor3 = currentTheme.colors.crust,
						Border = 2,
						BorderColor3 = currentTheme.colors.text,
						CornerRadius = UDim.new(0.2, 0),
						Text = "Continue",
						TextScaled = true,
						FontWeight = Enum.FontWeight.Bold,
						Reactive = true,

						[Fusion.OnEvent "Activated"] = function()
							if scope.peek(props.currentVehicleFolder) == nil then
								log.warn("Please wait until the vehicle is fully loaded before using any inputs")
								return
							end
							props.cleanupCustomization()
							props.ReturnTo:set("customizer")
							props.Mode:set("main")
						end :: unknown,

						MaxTextSize = 35,
					},
				},
			},

			scope:base {
				Name = "Modify",
				Size = UDim2.fromOffset(300, 500),
				AnchorPoint = Vector2.new(0, 0.5),
				Position = UDim2.new(0, 10, 0.5, 0),
				BackgroundColor3 = currentTheme.colors.base,
				CornerRadius = UDim.new(0, 15),

				[Fusion.Children] = Fusion.Child {
					scope:New("UIScale") {
						Scale = scope:Computed(function(use)
							return use(props.viewportSize).X / 1920
						end),
					},

					scope:textLabel {
						Size = UDim2.fromOffset(280, 30),
						AnchorPoint = Vector2.new(0.5, 0),
						Position = UDim2.new(0.5, 0, 0, 10),
						Text = scope:Computed(function(use)
							local currentLocation = use(props.currentLocation)
							return if currentLocation ~= nil then currentLocation:gsub("_", " ") else ""
						end),
						TextScaled = true,
						FontWeight = Enum.FontWeight.Bold,

						MaxTextSize = 25,
					},

					scope:base {
						Size = UDim2.fromOffset(250, 40),
						AnchorPoint = Vector2.new(0.5, 1),
						Position = UDim2.new(0.5, 0, 1, -10),
						BackgroundTransparency = 1,

						[Fusion.Children] = Fusion.Child {
							scope:textButton {
								Size = UDim2.fromOffset(115, 40),
								BackgroundColor3 = currentTheme.colors.crust,
								Border = 2,
								Shadow = true,
								ShadowColor3 = currentTheme.colors.crust,
								CornerRadius = UDim.new(0, 10),
								Text = "Return",
								TextScaled = true,
								FontWeight = Enum.FontWeight.Medium,
								Reactive = true,
								Visible = scope:Computed(function(use)
									return use(props.currentlyConfiguring) ~= nil
								end),

								[Fusion.OnEvent "Activated"] = function()
									props.currentlyConfiguring:set(nil)
								end :: unknown,

								MaxTextSize = 25,
							},

							scope:textButton {
								Size = UDim2.fromOffset(115, 40),
								AnchorPoint = Vector2.new(1, 0),
								Position = UDim2.fromScale(1, 0),
								BackgroundColor3 = currentTheme.colors.blue,
								Border = 2,
								Shadow = true,
								ShadowColor3 = currentTheme.colors.blue,
								ButtonGlow = true,
								ButtonGlowColor3 = if currentTheme.colors.white == Color3.new(1, 1, 1)
									then currentTheme.colors.text
									else currentTheme.colors.base,
								CornerRadius = UDim.new(0, 10),
								Text = "Done",
								TextColor3 = currentTheme.colors.mantle,
								TextScaled = true,
								FontWeight = Enum.FontWeight.Medium,
								Reactive = true,
								Visible = scope:Computed(function(use)
									return use(props.currentlyConfiguring) ~= nil
								end),

								[Fusion.OnEvent "Activated"] = function()
									props.currentlyConfiguring:set(nil)
									props.currentLocation:set(nil)
									props.currentEquipment:set("")
								end :: unknown,

								MaxTextSize = 25,
							},

							scope:textButton {
								Size = UDim2.fromScale(1, 1),
								BackgroundColor3 = currentTheme.colors.crust,
								Border = 2,
								Shadow = true,
								ShadowColor3 = currentTheme.colors.crust,
								CornerRadius = UDim.new(0, 10),
								Text = "Remove Current",
								TextScaled = true,
								FontWeight = Enum.FontWeight.Medium,
								Reactive = true,
								Visible = scope:Computed(function(use)
									return use(props.currentEquipment) ~= "" and use(props.currentlyConfiguring) == nil
								end),

								[Fusion.OnEvent "Activated"] = function()
									local currentLocation = scope.peek(props.currentLocation)
									local currentVehicle = scope.peek(props.currentVehicleFolder)
									local outfitting = scope.peek(props.outfitting)
									if currentLocation == nil or currentVehicle == nil or outfitting == nil then
										return
									end
									if
										vehicle:changeEquipment(currentVehicle, outfitting, currentLocation)
										== false
									then
										log.warn("failed to change equipment")
										return
									end
									props.recomputeLightParts()
									props.currentEquipment:set("")
									props.currentlyConfiguring:set(nil)
								end :: unknown,

								MaxTextSize = 25,
							},
						},
					},

					scope:textLabel {
						Size = UDim2.fromOffset(280, 50),
						AnchorPoint = Vector2.new(0.5, 0.5),
						Position = UDim2.fromScale(0.5, 0.5),
						Text = "Select Equipment Location To Modify",
						TextSize = 20,
						FontWeight = Enum.FontWeight.Regular,
						TextWrapped = true,

						Visible = scope:Computed(function(use)
							return use(props.currentLocation) == nil
						end),
					},

					scope:New("ScrollingFrame") {
						Name = "Table",
						BackgroundTransparency = 1,
						Size = UDim2.fromOffset(300, 390),
						AnchorPoint = Vector2.new(0.5, 0),
						Position = UDim2.new(0.5, 0, 0, 50),
						ScrollBarThickness = 3,
						ScrollBarImageColor3 = currentTheme.colors.text,
						TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						AutomaticCanvasSize = Enum.AutomaticSize.Y,
						CanvasSize = UDim2.new(),

						[Fusion.Children] = {
							scope:Computed(function(use, scope: typeof(scope))
								local currentVehicle = use(props.currentVehicleFolder)
								local outfitting = use(props.outfitting)
								return if currentVehicle ~= nil and outfitting ~= nil
									then construct(scope, currentVehicle, outfitting, props)
									else nil
							end),
						},
					},

					scope:configuring {
						attachments = props.attachments,
						viewportSize = props.viewportSize,
						currentLocation = props.currentLocation,

						Mode = props.Mode,
						CurrentVehicle = props.CurrentVehicle,

						outfitting = props.outfitting,
						currentVehicleFolder = props.currentVehicleFolder,
						currentEquipment = props.currentEquipment,
						currentlyConfiguring = props.currentlyConfiguring,
						currentOptionData = props.currentOptionData,

						cleanupCustomization = props.cleanupCustomization,
						recomputeLightParts = props.recomputeLightParts,
					},
				},
			},
		},
	}
end


-- Redon Tech Emergency Vehicle Creator, MIT License
local log = require(script.Parent.log)
local types = require(script.Parent.types)
local vehicle = require(script.Parent.vehicle)
local ChangeHistoryService = game:GetService("ChangeHistoryService")
local LuaEncode = require(script.Parent.Parent.Packages.LuaEncode)
local Fusion = require(script.Parent.Parent.Packages.fusion)
local LuaEncodeSettings = {
	Prettify = false,
	OutputWarnings = false,
}
local exportHeader =
	"-- Redon Tech Emergency Vehicle Creator, MIT License\n\n--DO NOT MODIFY BELOW\n--SUPPORT WILL BE VOIDED IF MODIFIED\n\nreturn "

-- version format: minor.{if rc then 0 else 1}patch
local latestVersion = 0.092
-- please ensure to update handler/EVHEvent/configuration/lightSettings.luau if you update this

export type color = {
	name: string,
	partColor: Color3,
	lightColor: Color3,
	lightoColor: Color3,
}

export type lightSettings = {
	version: number,
	colors: { color },
	configuration: {
		shouldChangeNeons: boolean,
		shouldChangeLights: boolean,
		shouldChangeLightos: boolean,
		shouldChangeParticleEmitters: boolean,
		partTransparency: number,
	},
	lightGroups: {
		{
			name: string,
			lights: { string },
		}
	},
}

export type lightSettingsState = {
	colors: Fusion.Value<{ color }>,
	configuration: {
		shouldChangeNeons: Fusion.Value<boolean>,
		shouldChangeLights: Fusion.Value<boolean>,
		shouldChangeLightos: Fusion.Value<boolean>,
		shouldChangeParticleEmitters: Fusion.Value<boolean>,
		partTransparency: Fusion.Value<number>,
	},
	lightGroups: Fusion.Value<{ { name: Fusion.Value<string>, lights: Fusion.Value<{ string }> } }>,
}

export type chassisConfiguration = {
	lightSettings: lightSettings,
	functions: { types.elsFunction },
	sirens: { types.siren },
}

type chassisHandler = {
	cache: {
		[Model]: {
			data: chassisConfiguration,
			functions: ModuleScript,
			sirens: ModuleScript,
			lightSettings: ModuleScript,
		},
	},
	hasPluginInstalled: (self: chassisHandler, model: Model) -> (boolean, LocalScript?),
	hasEVCChassisPluginInstalled: (self: chassisHandler, model: Model) -> (boolean, LocalScript?),
	installPlugin: (self: chassisHandler, model: Model) -> boolean,
	getConfiguration: (self: chassisHandler, model: Model) -> chassisConfiguration?,
	saveConfiguration: (self: chassisHandler, model: Model) -> boolean,
	getLights: (currentConfiguration: chassisConfiguration, model: Instance) -> { string },
	defaults: {
		functions: types.elsFunction,
		modifier: types.siren,
		colors: { color },
	},
}

local chassisHandler = {
	cache = {},
	defaults = {
		functions = {
			weight = 0,
			name = "default",
			activations = {
				{
					type = "Standard",
					mode = "Cycle",
					pattern = 0,
					keybinds = { Enum.KeyCode.J, Enum.KeyCode.DPadLeft },
				},
			},
			patterns = { {
				flashers = {},
				faders = {},
				rotators = {},
			} },
		},
		modifier = {
			name = "default",
			type = "Modifier",
			keybinds = { Enum.KeyCode.T, Enum.KeyCode.Unknown },
		},
		colors = {
			{
				name = "Blue",
				partColor = Color3.fromRGB(110, 153, 202),
				lightColor = Color3.fromRGB(0, 0, 255),
				lightoColor = Color3.fromRGB(47, 71, 255),
			},
			{
				name = "Red",
				partColor = Color3.fromRGB(190, 104, 98),
				lightColor = Color3.fromRGB(255, 0, 0),
				lightoColor = Color3.fromRGB(185, 58, 60),
			},
			{
				name = "Yellow",
				partColor = Color3.fromRGB(226, 155, 64),
				lightColor = Color3.fromRGB(255, 200, 0),
				lightoColor = Color3.fromRGB(253, 194, 66),
			},
			{
				name = "White",
				partColor = Color3.fromRGB(255, 255, 255),
				lightColor = Color3.fromRGB(255, 255, 255),
				lightoColor = Color3.fromRGB(255, 255, 255),
			},
			{
				name = "Green",
				partColor = Color3.fromRGB(0, 255, 0),
				lightColor = Color3.fromRGB(0, 255, 0),
				lightoColor = Color3.fromRGB(75, 255, 75),
			},
			{
				name = "Purple",
				partColor = Color3.fromRGB(170, 0, 170),
				lightColor = Color3.fromRGB(200, 0, 200),
				lightoColor = Color3.fromRGB(188, 12, 211),
			},
		},
	},
} :: chassisHandler
table.freeze(chassisHandler.defaults)

--- Checks if the Emergency Vehicle Handler plugin is installed on the given model.
function chassisHandler.hasPluginInstalled(self: chassisHandler, model: Model): (boolean, LocalScript?)
	if self.cache[model] then
		return true
	end

	local vehicleType = vehicle:isValidVehicle(model)
	if vehicleType == false then
		return false
	end

	local tune
	if vehicleType == "a" or vehicleType == "ag" then
		tune = model:FindFirstChild("A-Chassis Tune")
	elseif vehicleType == "m" then
		tune = model:FindFirstChild("Tuner")
	elseif vehicleType == "mg" then
		tune = model:FindFirstChild("MG-Chassis tune")
	end

	if tune == nil then
		return false
	end
	local plugins = tune:FindFirstChild("Plugins")
	if plugins == nil then
		return false
	end
	local Plugin = plugins:FindFirstChild("Emergency Vehicle Handler") :: LocalScript?
	if Plugin == nil or Plugin:IsA("LocalScript") == false then
		return false
	end

	return true, Plugin
end

--- Checks if the EVC Chassis Plugin is installed on the given model.
--- This is a legacy plugin that was used before the Emergency Vehicle Handler plugin.
function chassisHandler.hasEVCChassisPluginInstalled(_: chassisHandler, model: Model): (boolean, LocalScript?)
	local vehicleType = vehicle:isValidVehicle(model)
	if vehicleType == false then
		return false
	end

	local tune
	if vehicleType == "a" or vehicleType == "ag" then
		tune = model:FindFirstChild("A-Chassis Tune")
	elseif vehicleType == "m" then
		tune = model:FindFirstChild("Tuner")
	elseif vehicleType == "mg" then
		tune = model:FindFirstChild("MG-Chassis tune")
	end

	if tune == nil then
		return false
	end
	local plugins = tune:FindFirstChild("Plugins")
	if plugins == nil then
		return false
	end
	local Plugin = plugins:FindFirstChild("EVCPlugin_Client") :: LocalScript?
	if Plugin == nil or Plugin:IsA("LocalScript") == false then
		return false
	end

	return true, Plugin
end

type oldSiren =
	{
		_Type: "Hold" | "Siren",
		Name: string,
		OverrideOtherSounds: boolean,
		Modifiers: {
			[string]: {
				Name: string,
				PlayNonModified: boolean,
				PlayOnModifierChange: boolean,
				Delay: number,
			},
		}?,
	}
	| {
		_Type: "Modifier",
		Name: string,
	}

--- Installs the Emergency Vehicle Handler plugin on the given model.
--- This will convert any old EVC Chassis Plugin data to the new format.
function chassisHandler.installPlugin(self: chassisHandler, model: Model): boolean
	local vehicleType = vehicle:isValidVehicle(model)
	if vehicleType == false then
		return false
	end

	local tune
	if vehicleType == "a" or vehicleType == "ag" then
		tune = model:FindFirstChild("A-Chassis Tune")
	elseif vehicleType == "m" then
		tune = model:FindFirstChild("Tuner")
	elseif vehicleType == "mg" then
		tune = model:FindFirstChild("MG-Chassis tune")
	end

	if tune == nil then
		return false
	end
	local plugins = tune:FindFirstChild("Plugins")
	if plugins == nil then
		return false
	end

	local newHandler = script.Parent.handler:Clone()
	newHandler.Name = "Emergency Vehicle Handler"

	local isOldPluginInstalled, oldPlugin = self:hasEVCChassisPluginInstalled(model)
	if isOldPluginInstalled == true and oldPlugin ~= nil then
		local oldSettings = oldPlugin:FindFirstChild("Settings", true) :: ModuleScript?
		if oldSettings == nil then
			log.warn("Old plugin installed but Settings not found")
			newHandler:Destroy()
			return false
		end
		local loadedSettings = (require :: any)(oldSettings)
		local secondaryKeybinds: { [Enum.KeyCode]: Enum.KeyCode } = {}
		if loadedSettings.SecondaryKeybinds ~= nil and typeof(loadedSettings.SecondaryKeybinds) == "table" then
			for keybind: Enum.KeyCode, secondaryKeybind: Enum.KeyCode in loadedSettings.SecondaryKeybinds do
				secondaryKeybinds[keybind] = secondaryKeybind
			end
		end

		local convertedSirens: { types.siren } = {}
		if loadedSettings.Sirens ~= nil and typeof(loadedSettings.Sirens) == "table" then
			for keybind: Enum.KeyCode, siren: oldSiren in loadedSettings.Sirens do
				if siren._Type ~= "Modifier" then
					local newSiren: types.siren = {
						name = siren.Name,
						type = siren._Type,
						behavoir = if siren.OverrideOtherSounds
							then "Overrides Other Sounds"
							else "Plays Simultaneously",
						keybinds = { keybind },
						modifiers = {},
					}
					if
						siren.Modifiers ~= nil
						and typeof(siren.Modifiers) == "table"
						and newSiren.type ~= "Modifier" -- satisfy the type engine
					then
						for modifierName, modifier in siren.Modifiers do
							table.insert(newSiren.modifiers, {
								name = modifierName,
								modifiedSirenName = modifier.Name,
								parentSoundBehavior = if modifier.PlayNonModified
									then "Play Parent Sound"
									else "Plays Simultaneously",
								behavoir = if modifier.PlayOnModifierChange
									then "Play Automatically"
									else "Require Parent Replay",
								delay = modifier.Delay,
							})
						end
					end

					for secondaryKeybind, originalKeybind in secondaryKeybinds do
						if originalKeybind == keybind then
							table.insert(newSiren.keybinds, secondaryKeybind)
						end
					end

					table.insert(convertedSirens, newSiren)
				elseif siren._Type == "Modifier" then
					local newModifier: types.siren = {
						name = siren.Name,
						type = "Modifier",
						keybinds = { keybind },
					}

					for secondaryKeybind, originalKeybind in secondaryKeybinds do
						if originalKeybind == keybind then
							table.insert(newModifier.keybinds, secondaryKeybind)
						end
					end

					table.insert(convertedSirens, newModifier)
				end
			end
		end
		newHandler.EVHEvent.configuration.sirens.Source = exportHeader .. LuaEncode(convertedSirens, LuaEncodeSettings)

		local convertedFunctions: { types.elsFunction } = {}
		local lightbarName = loadedSettings.LightbarName or loadedSettings.LightbarLocation or "Lightbar"
		local car = oldPlugin:FindFirstAncestorWhichIsA("Model")

		if car ~= nil then
			local body = car:FindFirstChild("Body")
			if body ~= nil then
				local lightbar = body:FindFirstChild(lightbarName)
				if lightbar ~= nil then
					local moduleStore = lightbar:FindFirstChild("ModuleStore")
					if moduleStore ~= nil then
						for _, functionFolder in moduleStore:GetChildren() do
							if functionFolder:IsA("Folder") then
								local newFunction: types.elsFunction = {
									weight = 0,
									name = functionFolder.Name,
									activations = {},
									patterns = {},
								}
								for _, patternFolder in functionFolder:GetChildren() do
									if patternFolder:IsA("Folder") then
										local newPattern: types.pattern = {
											flashers = {},
											faders = {},
											rotators = {},
										}
										for _, moduleScript in patternFolder:GetChildren() do
											if moduleScript:IsA("ModuleScript") then
												local loadedModule = (require :: any)(moduleScript)
												if typeof(loadedModule) ~= "table" then
													continue
												end

												local waitTime = 0.1
												if
													loadedModule.Settings ~= nil
													and typeof(loadedModule.Settings) == "table"
												then
													waitTime = loadedModule.Settings.WaitTime or waitTime
													if newFunction.weight == 0 then
														newFunction.weight = loadedModule.Settings.Weight or 0
													end
												end

												local newFlasher: types.flasherSection = {
													columns = {},
													rowCount = 1,
													waitTime = waitTime,
												}
												local sortedNames: { string } = {}
												local unsortedColumns: { [string]: { [any]: any } } = {}
												if
													loadedModule.Lights ~= nil
													and typeof(loadedModule.Lights) == "table"
												then
													for lightName, lightData in loadedModule.Lights do
														local newColumn = {
															lightName = lightName,
															rows = {},
														}
														for _, color in lightData do
															table.insert(newColumn.rows, color)
														end

														table.insert(sortedNames, lightName)
														unsortedColumns[lightName] = newColumn
														if newFlasher.rowCount < #newColumn.rows then
															newFlasher.rowCount = #newColumn.rows
														end
													end

													table.sort(sortedNames)
													for _, lightName in sortedNames do
														table.insert(newFlasher.columns, unsortedColumns[lightName])
													end

													table.insert(newPattern.flashers, newFlasher)
												end

												if
													loadedModule.Rotators ~= nil
													and typeof(loadedModule.Rotators) == "table"
												then
													for lightName, lightData in loadedModule.Rotators do
														local newRotator: types.rotatorSection = {
															lightName = lightName,
															data = {},
														}
														for _, data in lightData do
															local newData = {
																type = "Angle" :: "Angle",
																color = data.Color,
																velocity = data.Velocity,
																angle = data.Angle,
															}
															table.insert(newRotator.data, newData)
														end
														table.insert(newPattern.rotators, newRotator)
													end
												end

												if
													loadedModule.Faders ~= nil
													and typeof(loadedModule.Faders) == "table"
												then
													for lightName, lightData in loadedModule.Faders do
														local newFader: types.faderSection = {
															lightName = lightName,
															data = {},
														}
														for _, data in lightData do
															local newData = {
																type = "Advanced" :: "Advanced",
																time = data.Time,
																goal = data.Transparency,
																color = data.Color,
																easingStyle = data.EasingStyle,
																easingDirection = data.EasingDirection,
															}
															table.insert(newFader.data, newData)
														end
														table.insert(newPattern.faders, newFader)
													end
												end
											end
										end

										table.insert(newFunction.patterns, newPattern)
									end
								end

								if loadedSettings.Keybinds ~= nil and typeof(loadedSettings.Keybinds) == "table" then
									for keybind, functionToCyle in loadedSettings.Keybinds do
										if functionToCyle == functionFolder.Name then
											local newActivation: types.activation = {
												type = "Standard",
												mode = "Cycle",
												pattern = 0,
												keybinds = { keybind },
											}

											for secondaryKeybind, originalKeybind in secondaryKeybinds do
												if originalKeybind == keybind then
													table.insert((newActivation :: any).keybinds, secondaryKeybind)
												end
											end

											table.insert(newFunction.activations, newActivation)
										end
									end
								end

								table.insert(convertedFunctions, newFunction)
							end
						end

						newHandler.EVHEvent.configuration.functions.Source = exportHeader
							.. LuaEncode(convertedFunctions, LuaEncodeSettings)
						moduleStore.Name = "NO LONGER USED, KEPT AS A BACKUP -> ModuleStore"
					end
				end
			end
		end

		oldPlugin:Destroy()
	end

	newHandler.Parent = plugins
	newHandler.Enabled = true
	ChangeHistoryService:SetWaypoint("Installed EVH")
	-- newHandler.EVHEvent.loader.Enabled = true
	return true
end

--- Retrieves the configuration for the given model.
--- If the configuration is already cached, it will return the cached data.
function chassisHandler.getConfiguration(self: chassisHandler, model: Model): chassisConfiguration?
	if self.cache[model] then
		return self.cache[model].data
	end

	local installed, Plugin = self:hasPluginInstalled(model)
	if installed == false or Plugin == nil then
		return
	end

	local event = Plugin:FindFirstChild("EVHEvent")
	if event == nil then
		log.warn("Plugin installed but EVHEvent not found")
		return
	end
	local configuration = event:FindFirstChild("configuration")
	if configuration == nil then
		log.warn("Plugin installed but configuration not found")
		return
	end

	local functions = configuration:FindFirstChild("functions") :: ModuleScript?
	local sirens = configuration:FindFirstChild("sirens") :: ModuleScript?
	local lightSettings = configuration:FindFirstChild("lightSettings") :: ModuleScript?
	if
		functions == nil
		or sirens == nil
		or lightSettings == nil
		or functions:IsA("ModuleScript") == false
		or sirens:IsA("ModuleScript") == false
		or lightSettings:IsA("ModuleScript") == false
	then
		log.warn("Plugin installed but configuration is invalid")
		return
	end

	--[[
		This is done to ensure that we are not requiring a reference
		but rather the actual data in the ModuleScript
		because for reasons beyond me, some things like lightgroups
		just wont show up in the data if its re-required after
		exporting new data to it
		DM parker if u dont understand this
		I personally hate this 😎
	]]
	--[[
		Hello, it is me again.
		I understand this now.
		Turns out I am a dumbass and the nightmare of this codebase
		has caused issues like the above, hopefully in the future
		this can be fixed so we can remove dumbass workarounds like
		this.
		- Parker, looking back at the codebase 6 months later
	]]
	local clonedFunctions = functions:Clone()
	local clonedSirens = sirens:Clone()
	local clonedLightSettings = lightSettings:Clone()
	local data = {
		functions = (require :: any)(clonedFunctions) :: { types.elsFunction },
		sirens = (require :: any)(clonedSirens) :: { types.siren },
		lightSettings = (require :: any)(clonedLightSettings) :: lightSettings,
	}
	clonedFunctions:Destroy()
	clonedSirens:Destroy()
	clonedLightSettings:Destroy()
	log.debug("Loaded configuration for", model.Name, data)

	--TODO: In the future abstract updating
	if data.lightSettings.version == nil then
		data.lightSettings.version = 0
	end
	local oldLightConfiguration = (data.lightSettings :: any).optimizations
	if oldLightConfiguration ~= nil then
		data.lightSettings.configuration = {
			shouldChangeNeons = oldLightConfiguration.shouldChangeNeons,
			shouldChangeLights = oldLightConfiguration.shouldChangeLights,
			shouldChangeLightos = oldLightConfiguration.shouldChangeLightos,
			shouldChangeParticleEmitters = false,
			partTransparency = 0.011,
		}
		(data.lightSettings :: any).optimizations = nil
		log.info("Updated lightSettings from optimizations to configuration", oldLightConfiguration, data.lightSettings)
	end
	if data.lightSettings.configuration.shouldChangeParticleEmitters == nil then
		data.lightSettings.configuration.shouldChangeParticleEmitters = false
	end
	if data.lightSettings.version ~= latestVersion then
		local oldLoader = event:FindFirstChild("loader")
		if oldLoader ~= nil then
			oldLoader:Destroy()
		end
		local newLoader = script.Parent.handler.EVHEvent.loader:Clone()
		newLoader.Parent = event

		Plugin.Source = script.Parent.handler.Source
		ChangeHistoryService:SetWaypoint("Updated EVH")
	end
	data.lightSettings.version = latestVersion

	--TODO: in the future abstract this error checking
	for _, currentFunction in data.functions do
		for _, pattern in currentFunction.patterns do
			if pattern.flashers == nil then
				pattern.flashers = {}
			end
			if pattern.faders == nil then
				pattern.faders = {}
			end
			if pattern.rotators == nil then
				pattern.rotators = {}
			end

			for _, flasher in pattern.flashers do
				if flasher.columns == nil then
					flasher.columns = {}
				end
				if flasher.rowCount == nil then
					flasher.rowCount = 1
				end
				if flasher.waitTime == nil then
					flasher.waitTime = 0.1
				end
			end

			for _, fader in pattern.faders do
				if fader.lightName == nil then
					fader.lightName = "LightNameCorrupted"
				end
				if fader.data == nil then
					fader.data = {}
				end
			end

			for _, rotator in pattern.rotators do
				if rotator.lightName == nil then
					rotator.lightName = "LightNameCorrupted"
				end
				if rotator.data == nil then
					rotator.data = {}
				end
			end
		end
	end

	self.cache[model] = {
		data = data,
		functions = functions,
		sirens = sirens,
		lightSettings = lightSettings,
	}
	return data
end

--- Saves the current configuration for the given model.
function chassisHandler.saveConfiguration(self: chassisHandler, model: Model): boolean
	local configuration = self.cache[model]
	if configuration == nil then
		return false
	end

	local functionsSourceBase = LuaEncode(configuration.data.functions, LuaEncodeSettings)
	local sirensSourceBase = LuaEncode(configuration.data.sirens, LuaEncodeSettings)
	local lightSettingsSourceBase = LuaEncode(configuration.data.lightSettings, LuaEncodeSettings)

	log.debug(
		"Saved configuration for",
		model.Name,
		configuration.data,
		{ functionsSourceBase },
		{ sirensSourceBase },
		{ lightSettingsSourceBase }
	)
	configuration.functions.Source = exportHeader .. functionsSourceBase
	configuration.sirens.Source = exportHeader .. sirensSourceBase
	configuration.lightSettings.Source = exportHeader .. lightSettingsSourceBase
	ChangeHistoryService:SetWaypoint("Saved configuration")

	return true
end

--TODO: Create some algorithm to detect possible lights that already havent been used.
--- Retrieves the list of possible lights from the current configuration.
--- This includes lights from the configuration, light groups, and any other light names referenced.
function chassisHandler.getLights(currentConfiguration: chassisConfiguration, model: Instance): { string }
	local possibleLights = {}
	for _, functions in currentConfiguration.functions do
		for _, pattern in functions.patterns do
			for _, flasher in pattern.flashers do
				for _, columns in flasher.columns do
					if table.find(possibleLights, columns.lightName) == nil then
						table.insert(possibleLights, columns.lightName)
					end
				end
			end

			for _, fader in pattern.faders do
				if table.find(possibleLights, fader.lightName) == nil then
					table.insert(possibleLights, fader.lightName)
				end
			end

			for _, rotator in pattern.rotators do
				if table.find(possibleLights, rotator.lightName) == nil then
					table.insert(possibleLights, rotator.lightName)
				end
			end
		end
	end

	if vehicle:isValidVehicle(model) then
		if vehicle:isVehicleCustomizable(model) then
			local outfittingFolder = vehicle:getOutfitting(model)
			if outfittingFolder ~= nil then
				for _, lightPart in outfittingFolder.model:GetDescendants() do
					if lightPart:IsA("BasePart") and lightPart.Parent ~= nil and lightPart.Parent.Name == "_LIGHTS" then
						if table.find(possibleLights, lightPart.Name) == nil then
							table.insert(possibleLights, lightPart.Name)
						end
					end
				end
			end
		end
	end

	for _, lightGroup in currentConfiguration.lightSettings.lightGroups do
		local possibleIndex = table.find(possibleLights, lightGroup.name)
		if possibleIndex ~= nil then
			table.remove(possibleLights, possibleIndex)
		end
	end

	table.sort(possibleLights)
	return possibleLights
end

return chassisHandler


-- Redon Tech Emergency Vehicle Creator, MIT License
local log = require(script.Parent.log)
local types = require(script.Parent.types)
local ChangeHistoryService = game:GetService("ChangeHistoryService")
local Fusion = require(script.Parent.Parent.Packages.fusion)

-- TEMPLATES
local scriptTemplate = [[
--\[\[
	Emergency Vehicle Creator
    Support not provided for these files.

	Redon Tech 2023-2025
	EVC V3
--\]\]

local Car = script.Parent.Parent.Parent
local Lightbar = script.Parent

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

-- Colors, feel free to change
local Colors = {
    Color3.fromRGB(110, 153, 202), -- Blue
    Color3.fromRGB(190, 104, 98), -- Red
    Color3.fromRGB(226, 155, 64), -- Yellow
    Color3.fromRGB(255, 255, 255), -- White
    Color3.fromRGB(0, 255, 0), -- Green
    Color3.fromRGB(170, 0, 170), -- Purple
}

-- By default this light function will work with most light types, modify to make it if need be
local function light(LightName, Color)
	local Light = Lightbar[LightName]
	if Color == 0 then
		for i, v in pairs(Light:GetDescendants()) do
			if v:IsA("GuiObject") then
				v.Visible = false
			elseif v:IsA("Light") or v:IsA("SurfaceGui") then
				v.Enabled = false
			elseif v:IsA("ParticleEmitter") then
				v.Transparency = NumberSequence.new(1)
			end
		end
		Light.Transparency = 1
	else
		for i, v in pairs(Light:GetDescendants()) do
			if v:IsA("GuiObject") then
				v.Visible = true
				v.ImageColor3 = Colors[Color]
			elseif v:IsA("Light") then
				v.Enabled = true
				v.Color = Colors[Color]
			elseif v:IsA("SurfaceGui") then
				v.Enabled = true
			elseif v:IsA("ParticleEmitter") then
				v.Transparency = NumberSequence.new(0)
				v.Color = ColorSequence.new(Colors[Color])
			end
		end
		Light.Transparency = 0
		Light.Color = Colors[Color]
	end
end

REPLACE_WITH_ROTATOR_REGISTER

-- Main Loop
--------------
-- To use the function above do
--      light("L1", 0)
-- The above will turn off said light
--      light("L1", 1)
-- The above will turn on said light and change its color to said color defined in the color table
--------------
while RunService.Heartbeat:Wait() do
]]

-- TODO: get around to implementing rotators/faders
scriptTemplate:gsub("REPLACE_WITH_ROTATOR_REGISTER", "")
scriptTemplate:gsub("REPLACE_WITH_TWEENSERVICE", "")

export type customExport = {
	export: (flashers: { types.flasherSection }) -> (),
}

local customExport = {} :: customExport

function customExport.export(flashers)
	local model = Instance.new("Model")
	model.Name = "EVC_Export"

	local lights = {}
	for sectionId, sectionData in flashers do
		local container = Instance.new("Script")
		container.Name = `Script_{sectionId}`
		local code = scriptTemplate

		for rowId = 1, sectionData.rowCount do
			code ..= `\n\n\t-- Row {rowId}`

			for _, columnData in sectionData.columns do
				code ..= `\t\nlight("{columnData.lightName}", {columnData.rows[rowId]})`
			end

			code ..= `\t\ntask.wait({sectionData.waitTime})`
		end

		container.Source = code
		container.Parent = model

		for _, columnData in sectionData.columns do
			if not table.find(lights, columnData.lightName) then
				table.insert(lights, columnData.lightName)
			end
		end
	end

	local offset = Vector3.new(0, 10, 0)
	for _, lightname in lights do
		local part = Instance.new("Part")
		part.Parent = model
		part.Name = lightname
		part.TopSurface = Enum.SurfaceType.Smooth
		part.BottomSurface = Enum.SurfaceType.Smooth
		part.Material = Enum.Material.Neon
		part.BrickColor = BrickColor.new("Institutional white")
		part.Anchored = true
		part.CanCollide = false
		part.Size = Vector3.new(0.75, 0.3, 0.1)
		offset += Vector3.new(0.85, 0, 0)
		part.Position = offset
	end

	model.Parent = workspace
	ChangeHistoryService:SetWaypoint("EVC Custom Export")
end

return customExport


-- Redon Tech Emergency Vehicle Creator, MIT License

--DO NOT MODIFY BELOW
--SUPPORT WILL BE VOIDED IF MODIFIED

return {
	{
		activations = {
			{
				mode = "Cycle",
				type = "Standard",
				pattern = 0,
				keybinds = {
					Enum.KeyCode.J,
					Enum.KeyCode.DPadLeft,
				},
			},
		},
		patterns = {
			{
				faders = {},
				flashers = {},
				rotators = {},
			},
		},
		name = "Stages",
		weight = 1,
	},
	{
		name = "Traffic Advisor",
		patterns = {
			{
				faders = {},
				flashers = {},
				rotators = {},
			},
		},
		activations = {
			{
				type = "Standard",
				mode = "Cycle",
				pattern = 0,
				keybinds = {
					Enum.KeyCode.K,
					Enum.KeyCode.DPadRight,
				},
			},
		},
		weight = 2,
	},
}


-- Redon Tech Emergency Vehicle Creator, MIT License

--DO NOT MODIFY BELOW
--SUPPORT WILL BE VOIDED IF MODIFIED

return {
	version = 0.092,
	colors = {
		{
			name = "Blue",
			partColor = Color3.fromRGB(110, 153, 202),
			lightColor = Color3.fromRGB(0, 0, 255),
			lightoColor = Color3.fromRGB(47, 71, 255),
		},
		{
			name = "Red",
			partColor = Color3.fromRGB(190, 104, 98),
			lightColor = Color3.fromRGB(255, 0, 0),
			lightoColor = Color3.fromRGB(185, 58, 60),
		},
		{
			name = "Yellow",
			partColor = Color3.fromRGB(226, 155, 64),
			lightColor = Color3.fromRGB(255, 200, 0),
			lightoColor = Color3.fromRGB(253, 194, 66),
		},
		{
			name = "White",
			partColor = Color3.fromRGB(255, 255, 255),
			lightColor = Color3.fromRGB(255, 255, 255),
			lightoColor = Color3.fromRGB(255, 255, 255),
		},
		{
			name = "Green",
			partColor = Color3.fromRGB(0, 255, 0),
			lightColor = Color3.fromRGB(0, 255, 0),
			lightoColor = Color3.fromRGB(75, 255, 75),
		},
		{
			name = "Purple",
			partColor = Color3.fromRGB(170, 0, 170),
			lightColor = Color3.fromRGB(200, 0, 200),
			lightoColor = Color3.fromRGB(188, 12, 211),
		},
	},
	configuration = {
		shouldChangeNeons = true,
		shouldChangeLights = true,
		shouldChangeLightos = true,
		shouldChangeParticleEmitters = false,
		partTransparency = 0.011,
	},
	lightGroups = {},
}


-- Redon Tech Emergency Vehicle Creator, MIT License

--DO NOT MODIFY BELOW
--SUPPORT WILL BE VOIDED IF MODIFIED

return {
	{
		type = "Siren",
		name = "Wail",
		keybinds = {
			Enum.KeyCode.R,
			Enum.KeyCode.Unknown,
		},
		behavoir = "Overrides Other Sounds",
		modifiers = {},
	},
	{
		type = "Siren",
		name = "Yelp",
		keybinds = {
			Enum.KeyCode.T,
			Enum.KeyCode.Unknown,
		},
		behavoir = "Overrides Other Sounds",
		modifiers = {},
	},
	{
		type = "Siren",
		name = "Priority",
		keybinds = {
			Enum.KeyCode.Y,
			Enum.KeyCode.Unknown,
		},
		behavoir = "Overrides Other Sounds",
		modifiers = {},
	},
	{
		type = "Hold",
		name = "Horn",
		keybinds = {
			Enum.KeyCode.H,
			Enum.KeyCode.Unknown,
		},
		behavoir = "Overrides Other Sounds",
		modifiers = {},
	},
}


-- Redon Tech Emergency Vehicle Creator, MIT License
local version = script.Parent:WaitForChild("version").Value
require(script.Parent:WaitForChild("modules"):WaitForChild("main"))(version)


-- Redon Tech Emergency Vehicle Creator, MIT License

-- version format: minor.{if rc then 1 else 0}patch
local types = require(script.modules.types)
local configuration = script.Parent.configuration
local lightSettingsModule = configuration:WaitForChild("lightSettings")
local lightSettings = (require :: any)(lightSettingsModule) :: types.lightSettings
local version = lightSettings.version or 0
local versionInstance = Instance.new("NumberValue")
versionInstance.Name = "version"
versionInstance.Value = version
versionInstance.Parent = script

-- TODO: Detect studio and run on server
-- Trigger the start of handshaking
local initiator = script.EVHClient
local newContainer = Instance.new("Folder")
newContainer.Name = `EmergencyVehicleHandler_{version}`
newContainer.Parent = workspace
versionInstance.Parent = newContainer
initiator.Parent = newContainer
script.modules.Parent = newContainer
initiator.Enabled = true

local Players = game:GetService("Players")
local HTTPService = game:GetService("HttpService")
local JointsService = game:GetService("JointsService")

local EVHEvent = script.Parent
local initialized = false
local car: Model
local driveSeat: VehicleSeat
local uuid: string
local sirenSounds: { Sound } = {}

local functions: { types.elsFunction } = {}
local keybindsToFunction: {
	{
		keybind: Enum.KeyCode,
		functionId: number,
		mode: "Cycle" | "Set Pattern",
		patternId: number?,
	}
} =
	{}
local sirens: { types.siren } = {}
local sirensByKeycode: {
	{
		keybind: Enum.KeyCode,
		mode: "Hold" | "Siren" | "Modifier",
		sirenId: number,
	}
} = {}
local sirenNames: { string } = {}

local function setupRotator(rotator: BasePart)
	--TODO: Allow to override usage of driveSeat so that rotators can be used in misc
	local motorPart = Instance.new("Part")
	motorPart.Name = `motor{rotator.Name}`
	motorPart.Size = Vector3.new(0.1, 0.1, 0.1)
	motorPart.CFrame = rotator.CFrame
	motorPart.Transparency = 1
	local weld = Instance.new("Weld")
	weld.Part0 = driveSeat
	weld.Part1 = motorPart
	weld.C0 = driveSeat.CFrame:Inverse() * driveSeat.CFrame
	weld.C1 = motorPart.CFrame:Inverse() * driveSeat.CFrame
	weld.Parent = driveSeat

	local inverse: Instance? = rotator:FindFirstChild("inverse")
	if inverse ~= nil and inverse:IsA("BasePart") == false then
		inverse = nil
	end

	local Center = if inverse ~= nil then CFrame.new((inverse :: BasePart).Position) else CFrame.new(rotator.Position)
	local XYZ = if inverse ~= nil
		then CFrame.Angles((inverse :: BasePart).CFrame:ToEulerAnglesXYZ())
		else CFrame.Angles(rotator.CFrame:ToEulerAnglesXYZ())
	local motor = Instance.new("Weld")
	motor.Name = "Weld"
	motor.Part0 = motorPart
	motor.Part1 = rotator
	motor.C0 = (motorPart.CFrame:Inverse() * Center) * XYZ
	motor.C1 = (rotator.CFrame:Inverse() * Center) * XYZ
	motor:SetAttribute("C1", motor.C1)
	motor.Parent = rotator

	for _, v in rotator:GetChildren() do
		if v:IsA("WeldConstraint") and v.Part1 == driveSeat then
			v:Destroy()
		end
	end

	for _, v in driveSeat:GetChildren() do
		if v:IsA("Weld") and v.Part1 == rotator then
			v:Destroy()
		end
	end

	for _, v in JointsService:GetDescendants() do
		if v:IsA("Weld") and v.Part1 == rotator then
			v:Destroy()
		end
	end

	motorPart.Parent = driveSeat
end

local function load()
	car = EVHEvent.Parent :: Model
	local possibleDriveSeat = car:WaitForChild("DriveSeat")
	if possibleDriveSeat:IsA("VehicleSeat") == false then
		return
	end
	driveSeat = possibleDriveSeat :: VehicleSeat
	uuid = HTTPService:GenerateGUID(false)
	car:SetAttribute("uuid", uuid)
	car:SetAttribute("version", version)

	local functionsModule = configuration:WaitForChild("functions")
	local sirensModule = configuration:WaitForChild("sirens")
	if functionsModule:IsA("ModuleScript") == false or sirensModule:IsA("ModuleScript") == false then
		return
	end

	functions = (require :: any)(functionsModule) :: { types.elsFunction }
	sirens = (require :: any)(sirensModule) :: { types.siren }

	for i, siren in sirens do
		if siren.keybinds == nil or siren.name == nil or siren.type == nil then
			continue
		end
		table.insert(sirenNames, i, siren.name)
		for _, keybind in siren.keybinds :: { Enum.KeyCode } do
			if keybind == Enum.KeyCode.Unknown then
				continue
			end
			table.insert(sirensByKeycode, {
				keybind = keybind,
				mode = siren.type,
				sirenId = i,
			})
		end
	end

	local lightTemplate = car:FindFirstChild("lightTemplate")
	if lightTemplate ~= nil then
		if lightTemplate:IsA("BasePart") == false then
			lightTemplate = nil
		end
	end

	local possibleLights = {}
	local possibleRotators = {}
	for functionId, currentFunction in functions do
		if
			currentFunction.activations == nil
			or currentFunction.patterns == nil
			or currentFunction.name == nil
			or currentFunction.weight == nil
		then
			continue
		end
		currentFunction.name = currentFunction.name:gsub("[^%a%d]", "")
		car:SetAttribute(currentFunction.name, 0)
		car:GetAttributeChangedSignal(currentFunction.name):Connect(function()
			local currentPatternId = car:GetAttribute(currentFunction.name)
			if currentPatternId == nil or typeof(currentPatternId) ~= "number" then
				car:SetAttribute(currentFunction.name, 0)
				return
			end

			if currentPatternId > #currentFunction.patterns then
				car:SetAttribute(currentFunction.name, 0)
				return
			end
		end)

		for _, activation in currentFunction.activations do
			if activation.type == "Standard" then
				if activation.keybinds then
					for _, keybind in activation.keybinds do
						if keybind == Enum.KeyCode.Unknown then
							continue
						end
						table.insert(keybindsToFunction, {
							keybind = keybind,
							functionId = functionId,
							mode = activation.mode :: "Cycle" | "Set Pattern",
							patternId = activation.pattern,
						})
					end
				end
			elseif
				activation.type == "Override"
				and activation.dependentFunction ~= nil
				and activation.dependentFunction ~= ""
				and activation.dependentPattern ~= nil
			then
				if activation.dependentFunction == "No Dependent Function" then
					activation.dependentFunction = nil :: any
					continue
				end
				car:GetAttributeChangedSignal(activation.dependentFunction):Connect(function()
					local dependentValue = car:GetAttribute(activation.dependentFunction)
					if dependentValue == nil or typeof(dependentValue) ~= "number" then
						return
					end
					if dependentValue >= activation.dependentPattern then
						car:SetAttribute(
							currentFunction.name,
							if car:GetAttribute(`shouldOverride{currentFunction.name}`) == true then 1 else 0
						)
					else
						car:SetAttribute(currentFunction.name, 0)
					end
				end)
			elseif activation.type == "Default" and activation.pattern ~= nil then
				car:SetAttribute(currentFunction.name, activation.pattern)
			end
		end

		for _, pattern in currentFunction.patterns do
			if pattern.flashers == nil or pattern.faders == nil or pattern.rotators == nil then
				pattern.flashers = {}
				pattern.faders = {}
				pattern.rotators = {}
			end
			for _, flasher in pattern.flashers do
				if flasher.columns == nil then
					flasher.columns = {}
				end
				for _, columns in flasher.columns do
					if columns.lightName == nil then
						continue
					end
					if table.find(possibleLights, columns.lightName) == nil then
						table.insert(possibleLights, columns.lightName)
					end
				end
			end

			for _, fader in pattern.faders do
				if fader.lightName == nil then
					continue
				end
				if table.find(possibleLights, fader.lightName) == nil then
					table.insert(possibleLights, fader.lightName)
				end
			end

			for _, rotator in pattern.rotators do
				if rotator.lightName == nil then
					continue
				end
				if table.find(possibleLights, rotator.lightName) == nil then
					table.insert(possibleLights, rotator.lightName)
				end
				if table.find(possibleRotators, rotator.lightName) == nil then
					table.insert(possibleRotators, rotator.lightName)
				end
			end
		end
	end
	for _, lightGroup in lightSettings.lightGroups do
		local isRotator = if table.find(possibleRotators, lightGroup.name) ~= nil then true else false
		for _, lightName in lightGroup.lights do
			if table.find(possibleLights, lightName) == nil then
				table.insert(possibleLights, lightName)
			end
			if isRotator then
				if table.find(possibleRotators, lightName) == nil then
					table.insert(possibleRotators, lightName)
				end
			end
		end
	end

	local exclusionParents = {}
	for _, possibleExclusion in car:GetDescendants() do
		if possibleExclusion:GetAttribute("EVH_Ignore") == true then
			table.insert(exclusionParents, possibleExclusion)
		end
	end

	for _, v in car:GetDescendants() do
		if v:IsA("BasePart") then
			local excluded = false
			for _, exclusionParent in exclusionParents do
				if v:IsDescendantOf(exclusionParent) then
					excluded = true
					break
				end
			end
			if excluded then
				continue
			end
			if table.find(possibleLights, v.Name) ~= nil then
				v:AddTag(`{uuid}_LIGHT`)
				if lightTemplate ~= nil then
					v.Material = (lightTemplate :: BasePart).Material
					for _, child in lightTemplate:GetChildren() do
						local newChild = child:Clone()
						newChild.Parent = v
					end
				end
			end
			if table.find(possibleRotators, v.Name) ~= nil then
				setupRotator(v)
			end
		elseif v:IsA("Sound") then
			-- TODO: add some way for other scripts to find what sounds we are using
			-- this could also be done by just searching for the defaultVolume attribute
			for _, sirenData in sirens do
				if sirenData.name ~= v.Name then
					if sirenData.type ~= "Modifier" then
						for _, modifierData in sirenData.modifiers do
							if modifierData.modifiedSirenName == v.Name then
								table.insert(sirenSounds, v)
								v:SetAttribute("defaultVolume", v.Volume)
								v:SetAttribute("parentSound", sirenData.name)
								break
							end
						end
					end

					continue
				end

				table.insert(sirenSounds, v)
				v:SetAttribute("defaultVolume", v.Volume)

				break
			end
		end
	end
	if lightTemplate ~= nil then
		lightTemplate:Destroy()
	end

	-- AG-Chassis Handling
	local Body = car:FindFirstChild("Body")
	if Body then
		local ELS = Body:FindFirstChild("ELS")
		if ELS then
			local PTRNS = ELS:FindFirstChild("PTRNS")
			if PTRNS then
				local ELSRunning = PTRNS:FindFirstChild("ELSRunning")
				local PatternNumber = PTRNS:FindFirstChild("PatternNumber")
				if ELSRunning and ELSRunning:IsA("BoolValue") and PatternNumber and PatternNumber:IsA("IntValue") then
					local function AGELSChange()
						for _, currentFunction in functions do
							for _, activation in currentFunction.activations do
								if activation.type == "AG-Event" then
									if activation.mode == "Stages" then
										car:SetAttribute(
											currentFunction.name,
											if ELSRunning.Value then PatternNumber.Value + 1 else 0
										)
									end
								end
							end
						end
					end
					ELSRunning:GetPropertyChangedSignal("Value"):Connect(AGELSChange)
					PatternNumber:GetPropertyChangedSignal("Value"):Connect(AGELSChange)
					AGELSChange()
				end

				local TARunning = PTRNS:FindFirstChild("TARunning")
				local TAPatternNumber = PTRNS:FindFirstChild("TAPatternNumber")
				if TARunning and TARunning:IsA("BoolValue") and TAPatternNumber and TAPatternNumber:IsA("IntValue") then
					local function AGTAChange()
						for _, currentFunction in functions do
							for _, activation in currentFunction.activations do
								if activation.type == "AG-Event" then
									if activation.mode == "Traffic Advisor" then
										car:SetAttribute(
											currentFunction.name,
											if TARunning.Value then TAPatternNumber.Value + 1 else 0
										)
									end
								end
							end
						end
					end
					TARunning:GetPropertyChangedSignal("Value"):Connect(AGTAChange)
					TAPatternNumber:GetPropertyChangedSignal("Value"):Connect(AGTAChange)
					AGTAChange()
				end
			end
		end
	end

	car:AddTag("EVHVehicle")
	initialized = true
end

-- Wait until we are in the car
if EVHEvent.Parent == nil or EVHEvent.Parent:IsA("Model") == false then
	local connection
	connection = EVHEvent.AncestryChanged:Connect(function(child: Instance, parent: Instance?)
		if child == EVHEvent and parent ~= nil and parent:IsA("Model") then
			connection:Disconnect()
			EVHEvent:Destroy()
			load()
		end
	end)
else
	load()
end

local function updateModifiers(siren: types.siren, sirenTriggered: boolean)
	if siren.type == "Modifier" then
		return
	end

	for _, modifier in siren.modifiers do
		local modifierId = table.find(sirenNames, modifier.name)
		if modifierId == nil then
			continue
		end
		local modifierData = sirens[modifierId]
		if modifierData == nil then
			continue
		end

		if modifierData.enabled == true and siren.enabled == true then
			if modifier.behavoir == "Play Automatically" or sirenTriggered == true then
				for _, sound in sirenSounds do
					if sound:IsA("Sound") and sound.Name == modifier.modifiedSirenName then
						sound:Play()
						sound.TimePosition += modifier.delay
					end
				end

				if modifier.parentSoundBehavior ~= "Plays Simultaneously" then
					for _, sound in sirenSounds do
						if sound:IsA("Sound") and sound.Name == siren.name then
							sound:Stop()
						end
					end
				end
			end
		else
			for _, sound in sirenSounds do
				if sound:IsA("Sound") and sound.Name == modifier.modifiedSirenName then
					sound:Stop()
				end
			end

			if modifier.parentSoundBehavior ~= "Plays Simultaneously" and siren.enabled == true then
				for _, sound in sirenSounds do
					if sound:IsA("Sound") and sound.Name == siren.name then
						sound:Play()
						sound.TimePosition -= modifier.delay
					end
				end
			end
		end
	end
end

local function changeSiren(siren: types.siren, state: boolean)
	if siren.type ~= "Modifier" then
		for _, sound in sirenSounds do
			if sound:IsA("Sound") then
				local sirenId = table.find(sirenNames, sound.Name)
				if sound.Name == siren.name then
					if state == true then
						sound:Play()
						local originalVolume = sound:GetAttribute("defaultVolume")
						if originalVolume ~= nil and typeof(originalVolume) == "number" then
							sound.Volume = originalVolume
						end
					else
						sound:Stop()
					end
				elseif siren.behavoir == "Overrides Other Sounds" and sirenId ~= nil then
					if state == true then
						if siren.type == "Hold" then
							sound.Volume = 0
						else
							local sirenData = sirens[sirenId]
							changeSiren(sirenData, false)
						end
					else
						if siren.type == "Hold" then
							local originalVolume = sound:GetAttribute("defaultVolume")
							if originalVolume ~= nil and typeof(originalVolume) == "number" then
								sound.Volume = originalVolume
							end
						end
					end
				elseif
					siren.behavoir == "Overrides Other Sounds" and sound:GetAttribute("parentSiren") ~= siren.name
				then
					if state == true then
						sound.Volume = 0
					else
						local originalVolume = sound:GetAttribute("defaultVolume")
						if originalVolume ~= nil and typeof(originalVolume) == "number" then
							sound.Volume = originalVolume
						end
					end
				end
			end
		end

		siren.enabled = state
		updateModifiers(siren, true)
	else
		siren.enabled = state
		for _, sirenData in sirens do
			updateModifiers(sirenData, false)
		end
	end

	for _, currentFunction in functions do
		for _, activation in currentFunction.activations do
			if
				activation.type == "Override"
				and activation.mode == "Siren Override"
				and activation.siren == siren.name
			then
				if activation.dependentFunction ~= nil and activation.dependentFunction ~= "" then
					if car:GetAttribute(activation.dependentFunction) >= activation.dependentPattern then
						car:SetAttribute(currentFunction.name, if state then 1 else 0)
					end
					car:SetAttribute(`shouldOverride{currentFunction.name}`, state)
				else
					car:SetAttribute(currentFunction.name, if state then 1 else 0)
				end
			end
		end
	end
end

script.Parent.OnServerEvent:Connect(function(player: Player, type: string, ...)
	if initialized == false then
		return
	end
	if driveSeat.Occupant == nil or player ~= Players:GetPlayerFromCharacter(driveSeat.Occupant.Parent) then
		return
	end

	if type == "Input" then
		local UserInputState: Enum.UserInputState, UserInputType: Enum.UserInputType, KeyCode: Enum.KeyCode = ...
		if UserInputState == nil or UserInputType == nil or KeyCode == nil then
			return
		end
		if
			typeof(UserInputState) ~= "EnumItem"
			or typeof(UserInputType) ~= "EnumItem"
			or typeof(KeyCode) ~= "EnumItem"
		then
			return
		end

		if UserInputState == Enum.UserInputState.Begin then
			for _, data in keybindsToFunction do
				if data.keybind == KeyCode then
					local currentFunction = functions[data.functionId]
					if data.mode == "Cycle" then
						local currentValue = car:GetAttribute(currentFunction.name)
						if currentValue == nil or typeof(currentValue) ~= "number" then
							continue
						end
						car:SetAttribute(currentFunction.name, currentValue + 1)
					elseif data.mode == "Set Pattern" then
						car:SetAttribute(currentFunction.name, data.patternId or 0)
					end
				end
			end

			for _, data in sirensByKeycode do
				if data.keybind == KeyCode then
					local siren = sirens[data.sirenId]
					if siren == nil then
						continue
					end
					changeSiren(
						siren,
						if siren.type ~= "Hold" then if siren.enabled == true then false else true else true
					)
				end
			end
		elseif UserInputState == Enum.UserInputState.End then
			for _, data in sirensByKeycode do
				if data.keybind == KeyCode then
					if data.mode == "Hold" then
						local siren = sirens[data.sirenId]
						if siren == nil then
							continue
						end
						changeSiren(siren, false)
					end
				end
			end
		end
	elseif type == "Values" then
		local Value: string, State: unknown = ...
		if Value == "PBrake" and typeof(State) == "boolean" then
			for _, currentFunction in functions do
				for _, activation in currentFunction.activations do
					if activation.type == "Override" and activation.mode == "Park" then
						if activation.dependentFunction ~= nil and activation.dependentFunction ~= "" then
							if car:GetAttribute(activation.dependentFunction) >= activation.dependentPattern then
								car:SetAttribute(currentFunction.name, if State then 1 else 0)
							end
							car:SetAttribute(`shouldOverride{currentFunction.name}`, State)
						else
							car:SetAttribute(currentFunction.name, if State then 1 else 0)
						end
					end
				end
			end
		elseif Value == "Brake" and typeof(State) == "number" then
			for _, currentFunction in functions do
				for _, activation in currentFunction.activations do
					if activation.type == "Override" and activation.mode == "Brake" then
						if activation.dependentFunction ~= nil and activation.dependentFunction ~= "" then
							if car:GetAttribute(activation.dependentFunction) >= activation.dependentPattern then
								car:SetAttribute(currentFunction.name, if State > 0 then 1 else 0)
							end
							car:SetAttribute(`shouldOverride{currentFunction.name}`, State > 0)
						else
							car:SetAttribute(currentFunction.name, if State > 0 then 1 else 0)
						end
					end
				end
			end
		elseif Value == "Gear" and typeof(State) == "number" then
			for _, currentFunction in functions do
				for _, activation in currentFunction.activations do
					if activation.type == "Override" and activation.mode == "Reverse" then
						if activation.dependentFunction ~= nil and activation.dependentFunction ~= "" then
							if car:GetAttribute(activation.dependentFunction) >= activation.dependentPattern then
								car:SetAttribute(currentFunction.name, if State < 0 then 1 else 0)
							end
							car:SetAttribute(`shouldOverride{currentFunction.name}`, State < 0)
						else
							car:SetAttribute(currentFunction.name, if State < 0 then 1 else 0)
						end
					end
				end
			end
		end
	elseif type == "AGSceneLights" then
		local Scene: boolean, AllyLeft: boolean, AllyRight: boolean, RearScene: boolean = ...
		if Scene == nil or AllyLeft == nil or AllyRight == nil or RearScene == nil then
			return
		end
		if
			typeof(Scene) ~= "boolean"
			or typeof(AllyLeft) ~= "boolean"
			or typeof(AllyRight) ~= "boolean"
			or typeof(RearScene) ~= "boolean"
		then
			return
		end

		for _, currentFunction in functions do
			for _, activation in currentFunction.activations do
				if activation.type == "AG-Event" then
					if activation.mode == "Scene" then
						car:SetAttribute(currentFunction.name, if Scene then 1 else 0)
					elseif activation.mode == "Ally Left" then
						car:SetAttribute(currentFunction.name, if AllyLeft then 1 else 0)
					elseif activation.mode == "Ally Right" then
						car:SetAttribute(currentFunction.name, if AllyRight then 1 else 0)
					elseif activation.mode == "Rear Scene" then
						car:SetAttribute(currentFunction.name, if RearScene then 1 else 0)
					end
				end
			end
		end
	elseif type == "UpdateFunction" then
		local functionName: string, pattern: number = ...
		if functionName == nil or pattern == nil or typeof(pattern) ~= "number" then
			return
		end
		-- This is to prevent setting other attributes that may not be EVC related
		for _, currentFunction in functions do
			if currentFunction.name == functionName then
				car:SetAttribute(currentFunction.name, pattern)
				break
			end
		end
	end
end)


--!strict
--!optimize 2
--!native
-- Redon Tech Emergency Vehicle Creator, MIT License

local types = require(script.Parent.Parent.types)
local TweenService = game:GetService("TweenService")

type faders = {
	returnToZero: { { vehicle: Instance, section: types.faderSection } },
	setLightTransparency: (
		vehicle: Instance,
		functionId: number,
		lightName: string,
		color: number,
		transpareny: number
	) -> (),
	resetLightTransparency: (vehicle: Instance, lightName: string) -> (),
	processPattern: (pattern: types.pattern) -> (),
	processReturnToZero: (deltaTime: number) -> (),
}
local faders = {
	returnToZero = {},
} :: faders

function faders.processPattern(pattern: types.pattern)
	for _, data in pairs(pattern.faders) do
		local currentTweens = data.data
		local tweenId = data.currentTween or 1
		if tweenId > #currentTweens then
			if currentTweens[tweenId] ~= nil and currentTweens[tweenId].type ~= "Wait" then
				data.previousGoal = currentTweens[tweenId].goal
			end
			tweenId = 1
		end
		local currentTween = currentTweens[tweenId]
		local currentPercentage = (
			math.min(pattern.patternTime % data.totalTime, currentTween.timeEnd) - currentTween.timeStart
		) / currentTween.time

		if currentTween.type ~= "Wait" and currentPercentage >= 0 then
			local previousGoal = data.previousGoal or 1
			local goal: number = currentTweens[tweenId].goal or 0

			data.currentValue = (goal - previousGoal)
					* TweenService:GetValue(
						currentPercentage,
						currentTween.easingStyle or Enum.EasingStyle.Linear,
						currentTween.easingDirection or Enum.EasingDirection.InOut
					)
				+ previousGoal

			faders.setLightTransparency(
				pattern.vehicle,
				pattern.functionId,
				data.lightName,
				currentTween.color or 0,
				data.currentValue or 0
			)
		end

		if currentPercentage >= 0.98 or currentPercentage <= 0 then
			if currentTweens[tweenId] ~= nil and currentTweens[tweenId].type ~= "Wait" then
				data.previousGoal = currentTweens[tweenId].goal
			end
			tweenId += 1
		end
		data.currentTween = tweenId
	end
end

function faders.processReturnToZero(deltaTime: number)
	for i, data in pairs(faders.returnToZero) do
		local lastTweenId = data.section.currentTween or 1
		if lastTweenId > #data.section.data then
			lastTweenId = #data.section.data
		end
		local lastTween = data.section.data[lastTweenId]
		local currentPercentage = data.section.currentPercentage or 0

		if currentPercentage >= 0.98 then
			table.remove(faders.returnToZero, i)
			data.section.currentPercentage = 0
			data.section.previousGoal = 1
			data.section.currentValue = 1
			faders.resetLightTransparency(data.vehicle, data.section.lightName)
			continue
		end

		currentPercentage += deltaTime / lastTween.time
		data.section.currentPercentage = currentPercentage

		local previousGoal = data.section.previousGoal or 0
		data.section.currentValue = 1
				* TweenService:GetValue(
					currentPercentage,
					lastTween.easingStyle or Enum.EasingStyle.Linear,
					lastTween.easingDirection or Enum.EasingDirection.InOut
				)
			+ previousGoal

		faders.setLightTransparency(
			data.vehicle,
			nil :: any,
			data.section.lightName,
			lastTween.color or 0,
			data.section.currentValue or 0
		)
	end
end

return faders


--!strict
--!optimize 2
--!native
-- Redon Tech Emergency Vehicle Creator, MIT License

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

type flashers = {
	flash: (vehicle: Instance, functionId: number, lightName: string, color: types.elsColor) -> (),
	processPattern: (pattern: types.pattern) -> (),
}
local flashers = {} :: flashers

function flashers.processPattern(pattern: types.pattern)
	for _, flasherSection in pairs(pattern.flashers) do
		local nextRow = math.floor(((pattern.patternTime / flasherSection.waitTime) % flasherSection.rowCount) + 1)
		if nextRow ~= flasherSection.currentRow then
			for _, column in pairs(flasherSection.columns) do
				flashers.flash(pattern.vehicle, pattern.functionId, column.lightName, column.rows[nextRow])
			end
			flasherSection.currentRow = nextRow
		end
	end
end

return flashers


--!strict
--!optimize 2
--!native
-- Redon Tech Emergency Vehicle Creator, MIT License

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

type rotators = {
	returnToZero: { { vehicle: Instance, section: types.rotatorSection } },
	flash: (vehicle: Instance, functionId: number, lightName: string, color: types.elsColor) -> (),
	setRotation: (vehicle: Instance, functionId: number, lightName: string, rad: number) -> (),
	processPattern: (pattern: types.pattern, deltaTime: number) -> (),
	processReturnToZero: () -> (),
}
local rotators = {
	returnToZero = {},
} :: rotators

function rotators.processPattern(pattern: types.pattern, deltaTime: number)
	for _, data in pairs(pattern.rotators) do
		local currentAngles = data.data
		local angleId = data.currentAngle or 1
		local currentPercentage = data.currentPercentage or 0
		if angleId > #currentAngles then
			angleId = 1
			currentPercentage = 0
		end

		data.currentAngle = angleId
		local currentAngle = currentAngles[angleId]
		if currentAngle.type == "Angle" then
			if currentAngle.color ~= 0 then
				rotators.flash(pattern.vehicle, pattern.functionId, data.lightName, currentAngle.color or 0)
			end
			local currentRotation = data.currentValue or 0
			local goal = math.rad(currentAngle.angle or 90)
			local velocity: number = math.abs(currentAngle.velocity or 0.1)

			local difference = goal - currentRotation
			if math.abs(difference) == 0 then
				data.currentAngle = if angleId ~= #currentAngles then angleId + 1 else 1
				data.previousGoal = goal
				currentPercentage = 0
			else
				currentRotation += math.clamp(difference, -velocity, velocity)
				currentPercentage = 1 - ((goal - currentRotation) / (goal - (data.previousGoal or 0) :: number))

				data.currentValue = currentRotation
				rotators.setRotation(pattern.vehicle, pattern.functionId, data.lightName, currentRotation)
			end
		elseif currentAngle.type == "Infinite" then
			if currentAngle.color ~= 0 then
				rotators.flash(pattern.vehicle, pattern.functionId, data.lightName, currentAngle.color or 0)
			end
			local currentRotation = data.currentValue or 0
			local velocity: number = currentAngle.velocity or 0.1

			currentRotation += (velocity * 60) * deltaTime
			data.currentValue = currentRotation % (math.pi * 2)
			rotators.setRotation(pattern.vehicle, pattern.functionId, data.lightName, currentRotation)
		else
			currentPercentage += deltaTime / (currentAngle.time or 0.1)
			if currentPercentage >= 0.98 then
				data.currentAngle = if angleId ~= #currentAngles then angleId + 1 else 1
				currentPercentage = 0
			end
		end
		data.currentPercentage = currentPercentage
	end
end

function rotators.processReturnToZero()
	for i, data in rotators.returnToZero do
		-- data.section.currentLightState = 0
		local lastAngle = data.section.data[data.section.currentAngle or 1]
		local currentRotation = (data.section.currentValue or 0) % (math.pi * 2)
		local velocity: number = math.abs(lastAngle.velocity or 0.1)

		local difference = -currentRotation
		if math.abs(difference) == 0 then
			data.section.currentAngle = 1
			data.section.previousGoal = 0
			data.section.currentPercentage = 0
			table.remove(rotators.returnToZero, i)
		else
			currentRotation += math.clamp(difference, -velocity, velocity)
			data.section.currentPercentage = 1 - (-currentRotation / (-(data.section.previousGoal or 0) :: number))

			data.section.currentValue = currentRotation
			rotators.setRotation(data.vehicle, nil :: any, data.section.lightName, currentRotation)
		end
	end
end

return rotators


--!strict
--#selene: allow(global_usage)
-- Redon Tech Emergency Vehicle Creator, MIT License

--[=[
	Ensures only one EVH is running at a time
	and that the running EVH is the latest version

    Edgecase: A newer verstion is inserted after handshake is done.
	To handle this, we will check allow that newer version to run
	its vehicles by itself
]=]
return function(version: number): boolean
	local uuid = game:GetService("HttpService"):GenerateGUID(false)

	if _G.EVHHANDSHAKE == nil or (_G.EVHHANDSHAKE.status == "handshaking" and version > _G.EVHHANDSHAKE.version) then
		_G.EVHHANDSHAKE = {
			version = version,
			uuid = uuid,
			status = "handshaking",
		}
	else
		return false
	end

	task.wait(1)
	if _G.EVHHANDSHAKE.uuid == uuid then
		_G.EVHHANDSHAKE.status = "done"
		_G.RUNNINGEVH = { version }
		return true
	else
		return false
	end
end


--!strict
--!optimize 2
-- Redon Tech Emergency Vehicle Creator, MIT License

local types = require(script.Parent:WaitForChild("types"))

--- Initializes a vehicle by loading its configuration and lights.
return function(vehicle: Instance): types.vehicle?
	if vehicle:IsA("Model") == false then
		return
	end

	local EVHEvent = vehicle:WaitForChild("EVHEvent")

	local configuration = EVHEvent:WaitForChild("configuration")

	local functions = configuration:WaitForChild("functions")
	local lightSettings = configuration:WaitForChild("lightSettings")
	if functions:IsA("ModuleScript") == false or lightSettings:IsA("ModuleScript") == false then
		return
	end

	local currentConfiguration = {
		functions = (require :: any)(functions) :: { types.elsFunction },
		lightSettings = (require :: any)(lightSettings) :: types.lightSettings,
	}
	local lights = {}
	local lightConnectedGroups = {}

	-- setup backwards compatibility
	local oldLightConfiguration = (currentConfiguration.lightSettings :: any).optimizations
	if oldLightConfiguration ~= nil then
		currentConfiguration.lightSettings.configuration = {
			shouldChangeNeons = oldLightConfiguration.shouldChangeNeons,
			shouldChangeLights = oldLightConfiguration.shouldChangeLights,
			shouldChangeLightos = oldLightConfiguration.shouldChangeLightos,
			partTransparency = 0.011,
		}
		(currentConfiguration.lightSettings :: any).optimizations = nil
	end

	-- get lights
	for functionIndex, currentFunction in currentConfiguration.functions do
		if currentFunction.patterns == nil or currentFunction.name == nil or currentFunction.weight == nil then
			table.remove(currentConfiguration.functions, functionIndex)
			continue
		end
		-- also since we are here, format the name properly
		currentFunction.name = currentFunction.name:gsub("[^%a%d]", "")

		for _, pattern in currentFunction.patterns do
			-- also since we are here, insert the time var
			pattern.patternTime = 0

			if pattern.flashers == nil and pattern.faders == nil and pattern.rotators == nil then
				pattern.flashers = {}
				pattern.faders = {}
				pattern.rotators = {}
			end
			for _, flasher in pattern.flashers do
				if flasher.columns == nil then
					flasher.columns = {}
				end
				for _, columns in flasher.columns do
					if columns.lightName == nil then
						columns.lightName = "corruptedLightName"
					end
					if lights[columns.lightName] == nil then
						lights[columns.lightName] = {
							isLightGroup = false,
							possibleFunctions = {},
							parts = {},
							currentState = 0,
						}
					end
				end
			end

			for _, fader in pattern.faders do
				if fader.lightName == nil then
					fader.lightName = "corruptedLightName"
				end
				if lights[fader.lightName] == nil then
					lights[fader.lightName] = {
						isLightGroup = false,
						possibleFunctions = {},
						parts = {},
						currentState = 0,
					}
				end
				local totalTime = 0
				for _, data in fader.data do
					if data.time == nil or data.type == nil then
						continue
					end
					data.timeStart = totalTime
					totalTime += data.time
					data.timeEnd = totalTime
				end
				fader.totalTime = totalTime
			end

			for _, rotator in pattern.rotators do
				if rotator.lightName == nil then
					rotator.lightName = "corruptedLightName"
				end
				if lights[rotator.lightName] == nil then
					lights[rotator.lightName] = {
						isLightGroup = false,
						possibleFunctions = {},
						parts = {},
						currentState = 0,
					}
				end
			end
		end
	end

	-- get light connected groups
	for _, lightGroup in currentConfiguration.lightSettings.lightGroups do
		if lightGroup.name == nil or lightGroup.lights == nil then
			continue
		end
		lights[lightGroup.name] = {
			isLightGroup = true,
			possibleFunctions = {},
			parts = {},
			currentState = 0,
		}
		for _, lightName in lightGroup.lights do
			if lightConnectedGroups[lightName] == nil then
				lightConnectedGroups[lightName] = {}
			end

			table.insert(lightConnectedGroups[lightName], lightGroup.name)

			if lights[lightName] == nil then
				lights[lightName] = {
					isLightGroup = false,
					possibleFunctions = {},
					parts = {},
					currentState = 0,
				}
			end
		end
	end

	return {
		configuration = currentConfiguration,
		lights = lights,
		lightConnectedGroups = lightConnectedGroups,
		connections = {},
		uuid = "",
	}
end