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

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

local handshake = require(script.Parent:WaitForChild("handshake"))
local initializeVehicle = require(script.Parent:WaitForChild("initializeVehicle"))
local types = require(script.Parent:WaitForChild("types"))
local els = script.Parent:WaitForChild("els")
local flashers = require(els:WaitForChild("flashers"))
local faders = require(els:WaitForChild("faders"))
local rotators = require(els:WaitForChild("rotators"))

local vehicles: { [Instance]: types.vehicle } = {}
local runningPatterns: { types.pattern } = {}
type light = {
	lights: { Light },
	colorableLightos: { { lighto: LayerCollector, images: { ImageLabel } } },
	lightos: { [string]: { { lighto: LayerCollector, images: { ImageLabel } } } },
	particleEmitters: { ParticleEmitter },
	connections: { RBXScriptConnection },
}
local lights: {
	[BasePart]: light,
} = {}
local version = 0
local isPrimary = false

local isBinded = false
local function bindToRender()
	if isBinded then
		return
	end
	isBinded = true
	RunService:BindToRenderStep(`EVHClient_{version}`, Enum.RenderPriority.Last.Value, function(deltaTime: number)
		for _, pattern in runningPatterns do
			pattern.patternTime += deltaTime
			faders.processPattern(pattern)
			rotators.processPattern(pattern, deltaTime)
			flashers.processPattern(pattern)
		end
		faders.processReturnToZero(deltaTime)
		rotators.processReturnToZero()

		if #runningPatterns == 0 and #faders.returnToZero == 0 and #rotators.returnToZero == 0 then
			RunService:UnbindFromRenderStep(`EVHClient_{version}`)
			isBinded = false
		end
	end)
end

-- Light Handling

--- Changes the color of a light and its associated parts.
local function changeColor(vehicle: Instance, functionId: number, lightName: string, color: number)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end
	local lightData = vehicleData.lights[lightName]
	local colorData = vehicleData.configuration.lightSettings.colors[color]
	if colorData == nil then
		return
	end

	for _, part in lightData.parts do
		if part.Name ~= lightName then
			if vehicleData.lights[part.Name].runningFunction ~= functionId then
				continue
			end
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeNeons == true then
			part.Color = colorData.partColor
		end
		local lightInstances = lights[part]
		if lightInstances == nil then
			continue
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeLights == true then
			for _, light in lightInstances.lights do
				light.Color = colorData.lightColor
			end
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeLightos == true then
			for _, lighto in lightInstances.colorableLightos do
				for _, image in lighto.images do
					image.ImageColor3 = colorData.lightoColor
				end
			end

			for colorName, nonColorableLightos in lightInstances.lightos do
				if colorName == colorData.name then
					for _, lighto in nonColorableLightos do
						lighto.lighto.Enabled = true
					end
				else
					for _, lighto in nonColorableLightos do
						lighto.lighto.Enabled = false
					end
				end
			end
		end
	end
end

--- Turns on or off a light.
local function setLight(vehicleData: types.vehicle, part: BasePart, state: boolean)
	if vehicleData.configuration.lightSettings.configuration.shouldChangeNeons == true then
		part.Transparency = if state == true
			then vehicleData.configuration.lightSettings.configuration.partTransparency
			else 1
	end
	local lightInstances = lights[part]
	if lightInstances == nil then
		return
	end
	if vehicleData.configuration.lightSettings.configuration.shouldChangeLights == true then
		for _, light in lightInstances.lights do
			light.Enabled = state
		end
	end
	if vehicleData.configuration.lightSettings.configuration.shouldChangeLightos == true then
		for _, lighto in lightInstances.colorableLightos do
			lighto.lighto.Enabled = state
		end

		if state == false then
			for _, nonColorableLightos in lightInstances.lightos do
				for _, lighto in nonColorableLightos do
					lighto.lighto.Enabled = false
				end
			end
		end
	end
	if vehicleData.configuration.lightSettings.configuration.shouldChangeParticleEmitters == true then
		for _, particleEmitter in lightInstances.particleEmitters do
			particleEmitter.Enabled = state
		end
	end
end

local function changeLight(vehicle: Instance, functionId: number, lightName: string, state: boolean)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end
	local lightData = vehicleData.lights[lightName]

	for _, part in lightData.parts do
		if part.Name ~= lightName then
			if vehicleData.lights[part.Name].runningFunction ~= functionId then
				continue
			end
		end

		setLight(vehicleData, part, state)
	end
end

--- Changes the color of a light and its associated parts, and toggles the light on or off.
local function flashLight(vehicle: Instance, functionId: number, lightName: string, color: number)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end
	local lightData = vehicleData.lights[lightName]
	if lightData.runningFunction ~= functionId then
		return
	end

	if color == 0 and lightData.currentState ~= 0 then
		changeLight(vehicle, functionId, lightName, false)
	elseif color ~= 0 and lightData.currentState == 0 then
		changeLight(vehicle, functionId, lightName, true)
		changeColor(vehicle, functionId, lightName, color)
	elseif color ~= lightData.currentState then
		changeColor(vehicle, functionId, lightName, color)
	end
	lightData.currentState = color

	for _, part in lightData.parts do
		if part.Name ~= lightName then
			if vehicleData.lights[part.Name].currentState ~= color then
				flashLight(vehicle, functionId, part.Name, color)
			end
		end
	end
end

--- Sets the transparency of a light and its associated parts.
local function setLightTransparency(
	vehicle: Instance,
	functionId: number,
	lightName: string,
	color: number,
	transparency: number
)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end
	local lightData = vehicleData.lights[lightName]
	local colorData = vehicleData.configuration.lightSettings.colors[color]
	if colorData == nil then
		return
	end
	if lightData.runningFunction ~= functionId and lightData.returnToZero ~= true then
		return
	end

	if color ~= lightData.currentState then
		changeColor(vehicle, functionId, lightName, color)
	end

	for _, part in lightData.parts do
		if part.Name ~= lightName then
			if vehicleData.lights[part.Name].runningFunction ~= functionId and lightData.returnToZero ~= true then
				continue
			end
			if vehicleData.lights[part.Name].currentState ~= color then
				changeColor(vehicle, functionId, part.Name, color)
			end
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeNeons == true then
			part.Transparency = transparency
		end
		local lightInstances = lights[part]
		if lightInstances == nil then
			continue
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeLights == true then
			for _, light in lightInstances.lights do
				light.Enabled = true
				light.Brightness = (light:GetAttribute("defaultBrightness") :: number) * (1 - transparency)
			end
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeLightos == true then
			for _, lighto in lightInstances.colorableLightos do
				lighto.lighto.Enabled = true
				for _, image in lighto.images do
					image.ImageTransparency = transparency
				end
			end

			for colorName, nonColorableLightos in lightInstances.lightos do
				if colorName == colorData.name then
					for _, lighto in nonColorableLightos do
						lighto.lighto.Enabled = true
						for _, image in lighto.images do
							image.ImageTransparency = transparency
						end
					end
				else
					for _, lighto in nonColorableLightos do
						lighto.lighto.Enabled = false
					end
				end
			end
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeParticleEmitters == true then
			for _, particleEmitter in lightInstances.particleEmitters do
				particleEmitter.Transparency = NumberSequence.new(transparency)
			end
		end
	end

	lightData.currentState = color
end

--- Resets the transparency of a light and its associated parts to their default values.
local function resetLightTransparency(vehicle: Instance, lightName: string)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end
	local lightData = vehicleData.lights[lightName]
	if lightData.returnToZero ~= true then
		return
	end

	for _, part in lightData.parts do
		if part.Name ~= lightName then
			if lightData.returnToZero ~= true then
				continue
			end
			local otherLightData = vehicleData.lights[part.Name]
			changeLight(vehicle, otherLightData.runningFunction :: any, lightName, false)
			otherLightData.currentState = 0
		end
		local lightInstances = lights[part]
		if lightInstances == nil then
			continue
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeLights == true then
			for _, light in lightInstances.lights do
				light.Brightness = light:GetAttribute("defaultBrightness") :: number
			end
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeLightos == true then
			for _, lighto in lightInstances.colorableLightos do
				for _, image in lighto.images do
					image.ImageTransparency = image:GetAttribute("defaultImageTransparency") :: number
				end
			end

			for _, nonColorableLightos in lightInstances.lightos do
				for _, lighto in nonColorableLightos do
					for _, image in lighto.images do
						image.ImageTransparency = image:GetAttribute("defaultImageTransparency") :: number
					end
				end
			end
		end
		if vehicleData.configuration.lightSettings.configuration.shouldChangeParticleEmitters == true then
			for _, particleEmitter in lightInstances.particleEmitters do
				particleEmitter.Transparency = NumberSequence.new(0)
			end
		end
	end
	changeLight(vehicle, lightData.runningFunction :: any, lightName, false)
	lightData.currentState = 0
end

flashers.flash = flashLight
rotators.flash = flashLight
faders.setLightTransparency = setLightTransparency
faders.resetLightTransparency = resetLightTransparency

--- Sets the rotation of a light and its associated parts.
local function setRotation(vehicle: Instance, functionId: number, lightName: string, rad: number)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end
	local lightData = vehicleData.lights[lightName]
	if lightData.runningFunction ~= functionId and lightData.returnToZero ~= true then
		return
	end

	for _, part in lightData.parts do
		if part.Name ~= lightName then
			if vehicleData.lights[part.Name].runningFunction ~= functionId and lightData.returnToZero ~= true then
				continue
			end
		end
		local weld = part:FindFirstChild("Weld")
		if weld ~= nil and weld:IsA("Weld") then
			weld.C1 = (weld:GetAttribute("C1") :: CFrame) * CFrame.Angles(0, 0, rad)
			weld:SetAttribute("rad", rad)
		end
	end
end

rotators.setRotation = setRotation

-- Vehicle Handling

--- Updates the rotation of a rotator section based on the weld's rad attribute.
local function updateRotatorRad(vehicleData: types.vehicle, rotator: types.rotatorSection)
	if #vehicleData.lights[rotator.lightName].parts ~= 0 then
		local light = vehicleData.lights[rotator.lightName].parts[1]
		local weld = light:FindFirstChild("Weld")
		if weld ~= nil then
			local rad = weld:GetAttribute("rad")
			if rad ~= nil and typeof(rad) == "number" then
				rad = rad % (math.pi * 2)
				rotator.currentValue = rad
				rotator.previousGoal = rad
			end
		end
	end
end

--- Updates the running function of a light based on the function ID and whether it is being added or removed.
--- If the light is a group, it will update all connected lights in that group
local function updateLightRunningFunction(vehicle: Instance, lightName: string, functionId: number, adding: boolean)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end

	local lightData = vehicleData.lights[lightName]

	if lightData.isLightGroup == true then
		for childLightName, light in vehicleData.lightConnectedGroups do
			if table.find(light, lightName) then
				updateLightRunningFunction(vehicle, childLightName, functionId, adding)
			end
		end
	end

	if adding == false then
		local positionInPossible = table.find(lightData.possibleFunctions, functionId)
		if positionInPossible then
			table.remove(lightData.possibleFunctions, positionInPossible)
		end

		if lightData.runningFunction == functionId then
			lightData.runningFunction = nil

			local highestFunction = nil
			local highestWeight = 0
			for _, possibleFunction in lightData.possibleFunctions do
				local possibleFunctionData = vehicleData.configuration.functions[possibleFunction]
				if possibleFunctionData.weight > highestWeight then
					highestFunction = possibleFunction
					highestWeight = possibleFunctionData.weight
				end
			end
			lightData.runningFunction = highestFunction
			if highestFunction ~= nil then
				table.remove(lightData.possibleFunctions, table.find(lightData.possibleFunctions, highestFunction))
				vehicleData.lights[lightName].returnToZero = true
				for _, pattern in vehicleData.configuration.functions[highestFunction].patterns do
					for _, rotator in pattern.rotators do
						updateRotatorRad(vehicleData, rotator)
						vehicleData.lights[rotator.lightName].returnToZero = false
					end
				end
			end
		end

		if lightData.runningFunction == nil and #lightData.possibleFunctions == 0 then
			flashLight(vehicle, nil :: any, lightName, 0)
			vehicleData.lights[lightName].returnToZero = true
		end
	elseif adding == true then
		if lightData.runningFunction == nil then
			lightData.runningFunction = functionId
		else
			local currentFunction = vehicleData.configuration.functions[functionId]
			local currentWeight = currentFunction.weight
			local runningFunction = vehicleData.configuration.functions[lightData.runningFunction]
			local runningWeight = runningFunction.weight

			if currentWeight > runningWeight then
				table.insert(lightData.possibleFunctions, lightData.runningFunction)
				lightData.runningFunction = functionId
			else
				table.insert(lightData.possibleFunctions, functionId)
			end
		end
	end
end

--- Updates the internal state of a vehicle's function based on the function name and ID.
local function vehicleFunctionUpdate(vehicle: Instance, functionName: string, functionId: number)
	local patternId = vehicle:GetAttribute(functionName)
	if patternId == nil or typeof(patternId) ~= "number" then
		return
	end

	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end
	local currentFunction = vehicleData.configuration.functions[functionId]

	for i, pattern: types.pattern in currentFunction.patterns do
		if i == patternId then
			continue
		end
		local runningId = table.find(runningPatterns, pattern)
		if runningId then
			-- flashers.removePattern(pattern)
			table.remove(runningPatterns, runningId)
		end

		for _, flasher in pattern.flashers do
			for _, columns in flasher.columns do
				updateLightRunningFunction(vehicle, columns.lightName, functionId, false)
			end
		end

		for _, fader in pattern.faders do
			updateLightRunningFunction(vehicle, fader.lightName, functionId, false)
			table.insert(faders.returnToZero, { vehicle = vehicle, section = fader })
		end

		for _, rotator in pattern.rotators do
			updateLightRunningFunction(vehicle, rotator.lightName, functionId, false)
			table.insert(rotators.returnToZero, { vehicle = vehicle, section = rotator })
		end
	end

	if patternId ~= 0 then
		local pattern = currentFunction.patterns[patternId]
		for _, flasher in pattern.flashers do
			for _, columns in flasher.columns do
				updateLightRunningFunction(vehicle, columns.lightName, functionId, true)
			end
		end

		for _, fader in pattern.faders do
			updateLightRunningFunction(vehicle, fader.lightName, functionId, true)
			vehicleData.lights[fader.lightName].returnToZero = false
		end

		for _, rotator in pattern.rotators do
			updateLightRunningFunction(vehicle, rotator.lightName, functionId, true)
			updateRotatorRad(vehicleData, rotator)
			vehicleData.lights[rotator.lightName].returnToZero = false
		end
		pattern.patternTime = 0

		for _, flasherSection in pairs(pattern.flashers) do
			flasherSection.currentRow = nil
		end

		table.insert(runningPatterns, pattern)
		bindToRender()
		-- flashers.addPattern(pattern)
	end
end

--- Registers the descendants added to a light
local function lightDescendantAdded(
	vehicleData: types.vehicle,
	lighto: Instance,
	lightData: light,
	colorNames: { string }
)
	if lighto:IsA("Light") and vehicleData.configuration.lightSettings.configuration.shouldChangeLights then
		lighto.Enabled = false
		lighto:SetAttribute("defaultBrightness", lighto.Brightness)
		table.insert(lightData.lights, lighto)
	elseif
		lighto:IsA("LayerCollector") and vehicleData.configuration.lightSettings.configuration.shouldChangeLightos
	then
		lighto.Enabled = false

		local images = {}
		for _, image in lighto:GetDescendants() do
			if image:IsA("ImageLabel") or image:IsA("ImageButton") then
				image:SetAttribute("defaultImageTransparency", image.ImageTransparency)
				table.insert(images, image :: ImageLabel)
			end
		end

		if table.find(colorNames, lighto.Name) == nil then
			table.insert(lightData.colorableLightos, { lighto = lighto, images = images })
		else
			if lightData.lightos[lighto.Name] == nil then
				lightData.lightos[lighto.Name] = {}
			end
			table.insert(lightData.lightos[lighto.Name], { lighto = lighto, images = images })
		end
	elseif
		lighto:IsA("ParticleEmitter")
		and vehicleData.configuration.lightSettings.configuration.shouldChangeParticleEmitters
	then
		lighto.Enabled = false
		lighto.Transparency = NumberSequence.new(0)
		table.insert(lightData.particleEmitters, lighto)
	elseif
		(lighto:IsA("ImageLabel") or lighto:IsA("ImageButton"))
		and vehicleData.configuration.lightSettings.configuration.shouldChangeLightos
	then
		local lightoCollector = lighto:FindFirstAncestorWhichIsA("LayerCollector")
		for _, colorableLighto in pairs(lightData.colorableLightos) do
			if
				colorableLighto.lighto == lightoCollector
				and table.find(colorableLighto.images, lighto :: ImageLabel) == nil
			then
				table.insert(colorableLighto.images, lighto :: ImageLabel)
			end
		end
	end
end

--- Adds a light to the vehicle data and sets up its connections.
local function addLight(vehicleData: types.vehicle, light: Instance)
	if light:IsA("BasePart") then
		table.insert(vehicleData.lights[light.Name].parts, light)
		if vehicleData.lightConnectedGroups[light.Name] ~= nil then
			for _, group in vehicleData.lightConnectedGroups[light.Name] do
				table.insert(vehicleData.lights[group].parts, light)
			end
		end

		local lightData = {
			lights = {},
			colorableLightos = {},
			lightos = {},
			particleEmitters = {},
			connections = {},
		}
		local colorNames = {}
		for _, color in vehicleData.configuration.lightSettings.colors do
			table.insert(colorNames, color.name)
		end
		for _, lighto in light:GetDescendants() do
			lightDescendantAdded(vehicleData, lighto, lightData, colorNames)
		end
		table.insert(
			lightData.connections,
			light.DescendantAdded:Connect(function(lighto: Instance)
				lightDescendantAdded(vehicleData, lighto, lightData, colorNames)
			end)
		)
		table.insert(
			lightData.connections,
			light.DescendantRemoving:Connect(function(lighto: Instance)
				if lighto:IsA("Light") then
					table.remove(lightData.lights, table.find(lightData.lights, lighto))
				elseif lighto:IsA("LayerCollector") then
					for _, colorableLighto in lightData.colorableLightos do
						if colorableLighto.lighto == lighto then
							table.remove(
								lightData.colorableLightos,
								table.find(lightData.colorableLightos, colorableLighto)
							)
							break
						end
					end

					for _, nonColorableLightos in lightData.lightos do
						for _, nonColorableLighto in nonColorableLightos do
							if nonColorableLighto.lighto == lighto then
								table.remove(nonColorableLightos, table.find(nonColorableLightos, nonColorableLighto))
								break
							end
						end
					end
				elseif lighto:IsA("ParticleEmitter") then
					table.remove(lightData.particleEmitters, table.find(lightData.particleEmitters, lighto))
				end
			end)
		)

		lights[light] = lightData

		setLight(vehicleData, light, false)
	end
end

--- Removes a light from the vehicle data and disconnects its connections.
local function removeLight(vehicleData: types.vehicle, light: Instance)
	if light:IsA("BasePart") then
		table.remove(vehicleData.lights[light.Name].parts, table.find(vehicleData.lights[light.Name].parts, light))
		if vehicleData.lightConnectedGroups[light.Name] ~= nil then
			for _, group in vehicleData.lightConnectedGroups[light.Name] do
				table.remove(vehicleData.lights[group].parts, table.find(vehicleData.lights[group].parts, light))
			end
		end
		for _, connection in lights[light].connections do
			connection:Disconnect()
		end
		lights[light] = nil
	end
end

--- Initializes a vehicle and its lights, setting up the necessary connections and data structures.
local function vehicleAdded(vehicle: Instance)
	if vehicles[vehicle] then
		return
	end

	if
		(isPrimary == true and vehicle:GetAttribute("version") <= version)
		or (isPrimary == false and vehicle:GetAttribute("version") == version)
	then
		local uuid = vehicle:GetAttribute("uuid")
		if uuid == nil or typeof(uuid) ~= "string" then
			return
		end

		local vehicleData = initializeVehicle(vehicle)
		if vehicleData then
			vehicleData.uuid = uuid

			table.insert(
				vehicleData.connections,
				CollectionService:GetInstanceAddedSignal(`{uuid}_LIGHT`):Connect(function(light: Instance)
					addLight(vehicleData, light)
				end)
			)
			for _, light in CollectionService:GetTagged(`{uuid}_LIGHT`) do
				addLight(vehicleData, light)
			end

			table.insert(
				vehicleData.connections,
				CollectionService:GetInstanceRemovedSignal(`{uuid}_LIGHT`):Connect(function(light: Instance)
					removeLight(vehicleData, light)
				end)
			)
			for functionId, functionData in vehicleData.configuration.functions do
				for _, pattern in functionData.patterns do
					pattern.vehicle = vehicle
					pattern.functionId = functionId
				end

				table.insert(
					vehicleData.connections,
					vehicle:GetAttributeChangedSignal(functionData.name):Connect(function()
						vehicleFunctionUpdate(vehicle, functionData.name, functionId)
					end)
				)
			end

			vehicles[vehicle] = vehicleData

			-- Check for existing function values
			for functionId, functionData in vehicleData.configuration.functions do
				vehicleFunctionUpdate(vehicle, functionData.name, functionId)
			end
		end
	end
end

--- Removes a vehicle and its associated data, disconnecting all connections.
local function vehicleRemoved(vehicle: Instance)
	local vehicleData = vehicles[vehicle]
	if vehicleData == nil then
		return
	end

	for _, connection in vehicleData.connections do
		connection:Disconnect()
	end

	vehicles[vehicle] = nil
end

--- Sets up the event listeners for vehicle addition and removal, and initializes the heartbeat functions.
local function setup()
	CollectionService:GetInstanceAddedSignal("EVHVehicle"):Connect(vehicleAdded)
	CollectionService:GetInstanceRemovedSignal("EVHVehicle"):Connect(vehicleRemoved)
	for _, vehicle in CollectionService:GetTagged("EVHVehicle") do
		vehicleAdded(vehicle)
	end
end

--- The main function that initializes the EVH system.
--- It performs a handshake to determine if this is the primary instance and sets up the necessary data structures.
return function(actualVersion: number)
	version = actualVersion
	if handshake(version) == true then
		isPrimary = true
	else
		if _G.EVHHANDSHAKE.version < version then
			if table.find(_G.RUNNINGEVH, version) then
				script.Parent.Parent:Destroy()
				return
			end

			_G.RUNNINGEVH[version] = true
			isPrimary = false
		else
			script.Parent.Parent:Destroy()
			return
		end
	end

	setup()
end


--!strict

export type vehicle = {
	configuration: chassisConfiguration,
	lights: lights,
	lightConnectedGroups: { [string]: { string } }, -- light: { group }
	connections: { RBXScriptConnection },
	uuid: string,
}

export type lights = {
	[string]: {
		isLightGroup: boolean,
		runningFunction: number?,
		possibleFunctions: { number },
		parts: { BasePart },
		currentState: number,
		returnToZero: boolean?,
	},
}

-- Configuration Types

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 chassisConfiguration = {
	lightSettings: lightSettings,
	functions: { elsFunction },
}

-- ELS Types

export type elsColor = number

export type flasherSection = {
	waitTime: number,
	rowCount: number,
	columns: { [number]: { lightName: string, rows: { elsColor } } },
	currentRow: number?,
}

export type faderSection = {
	lightName: string,
	data: {
		{
			type: "Simple" | "Advanced" | "Wait",
			time: number,
			timeStart: number,
			timeEnd: number,

			-- Non-Wait Specific
			goal: number?,
			color: elsColor?,

			-- Advanced Specific
			easingStyle: Enum.EasingStyle?,
			easingDirection: Enum.EasingDirection?,
		}
	},
	currentTween: number?,
	currentPercentage: number?,
	currentValue: number?,
	previousGoal: number?,
	totalTime: number,
}

export type rotatorSection = {
	lightName: string,
	data: {
		{
			type: "Angle" | "Infinite" | "Wait",
			-- Non-Wait Specific
			color: number?,
			velocity: number?,

			-- Angle Specific
			angle: number?,

			-- Wait Specific
			time: number?,
		}
	},
	currentAngle: number?,
	currentPercentage: number?,
	currentValue: number?,
	previousGoal: number?,
	currentLightState: number?,
}

export type pattern = {
	flashers: { flasherSection },
	faders: { faderSection },
	rotators: { rotatorSection },
	vehicle: Instance,
	functionId: number,
	patternTime: number,
}

export type selectedPattern = {
	functionId: number,
	patternId: number,
	pattern: pattern,
}

export type activation = {
	type: "Standard",
	mode: "Cycle" | "Set Pattern",
	pattern: number,
	keybinds: { Enum.KeyCode },
} | {
	type: "Default",
	pattern: number,
} | {
	type: "Override",
	mode: "Siren Override" | "Park" | "Brake" | "Reverse",
	siren: string?,
	dependentFunction: string,
	dependentPattern: number,
} | {
	type: "Conditional",
	mode: "Always Active" | "Keybind" | "Function" | "Siren",
	conditions: {
		{
			type: "Function",
			mode: "Is" | "Is Not",
			dependentFunction: string,
			dependentPattern: number,
		}
	},
} | {
	type: "AG-Event",
	mode: "Stages" | "Traffic Advisor" | "Scene" | "Ally Left" | "Ally Right" | "Rear Scene",
}

export type elsFunction = {
	weight: number,
	name: string,
	activations: { activation },
	patterns: { pattern },
}

-- Siren Types

export type modifier = {
	name: string,
	modifiedSirenName: string,
	parentSoundBehavior: "Play Parent Sound" | "Plays Simultaneously",
	behavoir: "Play Automatically" | "Require Parent Replay",
	delay: number,
}

export type siren = {
	name: string,
	type: "Siren" | "Hold",
	enabled: boolean?,
	behavoir: "Overrides Other Sounds" | "Plays Simultaneously",
	keybinds: { Enum.KeyCode },
	modifiers: { modifier },
} | {
	name: string,
	type: "Modifier",
	enabled: boolean?,
	keybinds: { Enum.KeyCode },
}

return {}


--[[
	 _____               _                     _______                 _
	|  __ \             | |                   |__   __|               | |
	| |__) |   ___    __| |   ___    _ __        | |      ___    ___  | |__
	|  _  /   / _ \  / _  |  / _ \  |  _ \       | |     / _ \  / __| |  _ \
	| | \ \  |  __/ | (_| | | (_) | | | | |      | |    |  __/ | (__  | | | |
	|_|  \_\  \___|  \__,_|  \___/  |_| |_|      |_|     \___|  \___| |_| |_|
	Emergency Vehicle Handler
    Apart of EVC V3
--]]
--[[
	MIT License

	Copyright (c) 2022-2025 Redon Tech

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

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

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

if plugin then
	return
end

local UserInputService = game:GetService("UserInputService")
local CarReference = script.Parent:WaitForChild("Car")
local Values = script.Parent:WaitForChild("Values")
if CarReference:IsA("ObjectValue") == false then
	error("[EVH ERROR]: Car ObjectValue not valid")
end

local Car = CarReference.Value :: Model
local EVHEvent = Car:WaitForChild("EVHEvent") :: RemoteEvent
if EVHEvent:IsA("RemoteEvent") == false then
	error("[EVH ERROR]: EVHEvent RemoteEvent not valid")
end

-- Overrides setup
if Values:IsA("Folder") then
	local PBrake = Values:WaitForChild("PBrake")
	local Brake = Values:WaitForChild("Brake")
	local Gear = Values:WaitForChild("Gear")

	-- Parking Brake Override
	if PBrake:IsA("BoolValue") then
		PBrake:GetPropertyChangedSignal("Value"):Connect(function()
			EVHEvent:FireServer("Values", "PBrake", PBrake.Value)
		end)
		EVHEvent:FireServer("Values", "PBrake", PBrake.Value)
	else
		warn("[EVH WARNING]: PBrake BoolValue not valid, some overrides may not work")
	end

	-- Brake Override
	if Brake:IsA("NumberValue") then
		Brake:GetPropertyChangedSignal("Value"):Connect(function()
			EVHEvent:FireServer("Values", "Brake", Brake.Value)
		end)
		EVHEvent:FireServer("Values", "Brake", Brake.Value)
	else
		warn("[EVH WARNING]: Brake NumberValue not valid, some overrides may not work")
	end

	-- Reverse Override
	if Gear:IsA("NumberValue") then
		Gear:GetPropertyChangedSignal("Value"):Connect(function()
			EVHEvent:FireServer("Values", "Gear", Gear.Value)
		end)
		EVHEvent:FireServer("Values", "Gear", Gear.Value)
	else
		warn("[EVH WARNING]: Gear NumberValue not valid, some overrides may not work")
	end
else
	warn("[EVH WARNING]: Values Folder not valid, some overrides may not work")
end

-- Input Handling

local function input(InputObject: InputObject, gameProcessedEvent: boolean)
	if
		gameProcessedEvent == false
		and (
			InputObject.UserInputType == Enum.UserInputType.Keyboard
			or InputObject.UserInputType == Enum.UserInputType.Gamepad1
		)
	then
		EVHEvent:FireServer("Input", InputObject.UserInputState, InputObject.UserInputType, InputObject.KeyCode)
	end
end

UserInputService.InputBegan:Connect(input)
UserInputService.InputChanged:Connect(input)
UserInputService.InputEnded:Connect(input)

-- AG-Chassis Handling

local Events = Car:FindFirstChild("Events")
if Events then
	local SpecialEvent = Events:FindFirstChild("SpecialEvent") :: RemoteEvent
	if SpecialEvent and SpecialEvent:IsA("RemoteEvent") then
		SpecialEvent.OnClientEvent:Connect(function(EventType: string, ...)
			if EventType == "LIGHT" then
				EVHEvent:FireServer("AGSceneLights", ...)
			end
		end)
	end
end


--# selene: allow(global_usage)
--[[
	 _____               _                     _______                 _
	|  __ \             | |                   |__   __|               | |
	| |__) |   ___    __| |   ___    _ __        | |      ___    ___  | |__
	|  _  /   / _ \  / _  |  / _ \  |  _ \       | |     / _ \  / __| |  _ \
	| | \ \  |  __/ | (_| | | (_) | | | | |      | |    |  __/ | (__  | | | |
	|_|  \_\  \___|  \__,_|  \___/  |_| |_|      |_|     \___|  \___| |_| |_|
	Emergency Vehicle Creator V3
	Plugin Loader
	MIT License
--]]

if not game:GetService("RunService"):IsEdit() then
	return
end

local VERSION = "3.0.0-rc.9"
local IS_DEV = false
if IS_DEV then
	game:GetService("LogService"):ClearOutput()
end

--[[

	Plugin Initialization

]]

--selene: allow(incorrect_standard_library_use)
local isRBXM = plugin.Name:find(".rbxm") ~= nil

local function getName(name: string): string
	if isRBXM == true then
		name ..= " (RBXM)"
	end
	return name
end

local name = getName("Emergency Vehicle Creator V3")
local description = "Easily create emergency vehicles with this plugin!"
local pluginIcon = "http://www.roblox.com/asset/?id=9953243250"
local buttonName = getName("EVC")

local button: PluginToolbarButton
-- TODO: Find a better way to make a global toolbar
if _G.RTPlugins and typeof(_G.RTPlugins) == "table" then
	if _G.RTPlugins.Buttons[name] then
		button = _G.RTPlugins.Buttons[name]
	else
		_G.RTPlugins.Buttons[name] = _G.RTPlugins.ToolBar:CreateButton(buttonName, description, pluginIcon)
		button = _G.RTPlugins.Buttons[name]
	end
else
	_G.RTPlugins = {
		ToolBar = plugin:CreateToolbar("Redon Tech Plugins"),
		Buttons = {},
	}

	_G.RTPlugins.Buttons[name] = _G.RTPlugins.ToolBar:CreateButton(buttonName, description, pluginIcon)
	button = _G.RTPlugins.Buttons[name]
end
button.ClickableWhenViewportHidden = true

--[[

	Run

]]

local root = script.Parent
-- Setup Logging
local log = require(script.log)
log.verbosity = if IS_DEV then log.level.debug else log.level.error

-- Setup Version
local version = require(script.version)
version.currentVersion = VERSION
version.currentVer = version:parseVersion(VERSION)
version:checkVersion()
local versionDisplay = VERSION
if isRBXM then
	if version.currentVer.build == nil then
		versionDisplay ..= "+local"
	else
		versionDisplay ..= ".local"
	end
end

-- Main Requirements
local pluginSettings = require(script.settings)
local Fusion = require(root.Packages.fusion)
local RedonUI = require(root.Packages["redon-ui"])
local types = require(script.types)

-- Setup Scope
local utils = require(script.utils)
local menu = require(script.Components.menu)
local viewport = require(script.Components.viewport)
local main = require(script.Components.main)
local popup = require(script.Components.popup)
local colorPicker = require(script.Components.base.colorPicker)
local scope = Fusion.scoped(Fusion, RedonUI.Util, RedonUI.Components, utils, {
	menu = menu,
	viewport = viewport,
	mainWidget = main,
	popup = popup,
})

table.insert(scope, function()
	log.debug("Cleaning vehicle cache")
	local cache = require(script.vehicle).cache
	for i, v in pairs(cache) do
		v.vehicle:Destroy()
		v.customization:Destroy()
		cache[i] = nil
	end
end)

pluginSettings:setup(scope)
if scope.peek(pluginSettings.values.debugMode) == true then
	log.verbosity = log.level.debug
end

-- Values
local enabled = scope:Value(isRBXM and IS_DEV)
local mode = scope:Value("menu" :: types.mode)
local currentVehicle: Fusion.Value<Model | string?> = scope:Value(nil)
local possibleVehicle: Fusion.Value<Model?> = scope:Value(nil)
local returnTo = scope:Value("menu" :: types.returnTo)
local theme = scope.peek(pluginSettings.values.theme)

-- UI Initialization
colorPicker(scope, { id = "evcColorPicker", initialColor = Color3.fromRGB(255, 255, 255) })
local activeScope: Fusion.Scope<typeof(scope)>? = nil
local function initUI()
	local themeToUse = nil
	if theme == "Auto" then
		if settings().Studio.Theme.Name == "Dark" then
			themeToUse = "Mocha"
		else
			themeToUse = "Latte"
		end
	else
		themeToUse = theme
	end

	activeScope = scope:innerScope()
	RedonUI.theme.theme:is(RedonUI.theme[themeToUse:lower()]):during(function()
		if activeScope == nil then
			return {}
		end
		local popupFrame, isPopupShown = activeScope.popup:container(scope)
		local menuWidget = activeScope:menu {
			Name = name,
			Version = versionDisplay,

			Enabled = enabled,
			Mode = mode,
			CurrentVehicle = currentVehicle,
			PossibleVehicle = possibleVehicle,
			ReturnTo = returnTo,
		} :: DockWidgetPluginGui
		local mainWidget = activeScope:mainWidget {
			Name = name,
			Version = versionDisplay,

			Enabled = enabled,
			Mode = mode,
			CurrentVehicle = currentVehicle,
			PossibleVehicle = possibleVehicle,
			ReturnTo = returnTo,
			PopupShown = isPopupShown,

			[Fusion.Children] = {
				popupFrame,
			},
		} :: DockWidgetPluginGui
		local viewportGui = activeScope:viewport {
			Name = name,
			Version = versionDisplay,

			Enabled = enabled,
			Mode = mode,
			CurrentVehicle = currentVehicle,
			ReturnTo = returnTo,
		}
		viewportGui.Parent = game:GetService("CoreGui")

		menuWidget:BindToClose(function()
			enabled:set(false)
		end)

		mainWidget:BindToClose(function()
			enabled:set(false)
		end)

		return {
			menuWidget,
			viewportGui,
		} :: { Instance }
	end)
end
initUI()

-- Theme Handling
settings().Studio.ThemeChanged:Connect(function()
	if activeScope and theme == "Auto" then
		activeScope:doCleanup()
		activeScope = nil
		initUI()
	end
end)

scope:Observer(pluginSettings.values.theme):onChange(function()
	if activeScope then
		theme = scope.peek(pluginSettings.values.theme)
		activeScope:doCleanup()
		activeScope = nil
		initUI()
	end
end)

-- Button Handling
button:SetActive(scope.peek(enabled)) -- If the plugin is reloaded, the button state should be reset
scope:Observer(enabled):onChange(function()
	button:SetActive(Fusion.peek(enabled))
end)

table.insert(
	scope,
	button.Click:Connect(function()
		enabled:set(not Fusion.peek(enabled))
	end)
)

--[[

	Cleanup

]]

plugin.Unloading:Connect(function()
	scope:doCleanup()
end)


-- Redon Tech Emergency Vehicle Creator, MIT License
local log = {
	level = {
		info = 1,
		warn = 2,
		error = 3,
		-- 4 and above is developer only
		debug = 4,
	},
	format = {
		info = "[EVC INFO]:",
		warn = "[EVC WARNING]:",
		error = "[EVC ERROR]:",
		debug = "[EVC DEBUG]:",
	},
	verbosity = 1,
}

--- Gets the location where a log was called from.
--- This is a workaround for the log showing this file as the source of the log
function log.getTraceback(): string
	local s, l = debug.info(3, "sl")
	return `{s}:{l}`
end

function log.info(...)
	if log.verbosity >= 1 then
		print(log.format.info, ...)
		if log.verbosity >= 4 then
			print(log.getTraceback())
		end
	end
end

function log.warn(...)
	if log.verbosity >= 2 then
		warn(log.format.warn, ...)
		if log.verbosity >= 4 then
			print(log.getTraceback())
		end
	end
end

--- Logs an error message and halts execution.
--- This function should be used for critical errors that require immediate attention.
function log.error(...)
	-- Errors should always be sent, if this is called it expects execution to halt
	error(`{log.format.error}{...}`, 2)
end

function log.debug(...)
	if log.verbosity >= 4 then
		print(log.format.debug, ...)
		if log.verbosity >= 4 then
			print(log.getTraceback())
		end
	end
end

return log


-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent
local plugin = root:FindFirstAncestorWhichIsA("Plugin") :: Plugin

local types = require(script.Parent.types)
local log = require(script.Parent.log)
local Fusion = require(root.Packages.fusion)

type settings = {
	scope: types.Scope,
	values: {
		theme: Fusion.Value<types.theme>,
		fakeBloom: Fusion.Value<boolean>,
		colorKeybinds: Fusion.Value<types.keybinds>,
		debugMode: Fusion.Value<boolean>,
		saveBackupMode: Fusion.Value<types.saveBackupMode>,
	},
	valueData: { { type: { string } | "boolean", title: string, description: string, key: string } },
	setup: (self: settings, scope: types.Scope) -> (),
}
local settings = {
	valueData = {
		{
			title = "Theme",
			description = "The theme of the UI",
			type = { "Auto", "Latte", "Frappe", "Macchiato", "Mocha" },
			key = "theme",
		},
		{
			title = "Fake Bloom",
			description = "Toggle the fake bloom applied in the UI when playing a pattern",
			type = "boolean",
			key = "fakeBloom",
		},
		{
			title = "Color Keybinds",
			description = "Choose which keybinds you would like to use to change colors",
			type = { "Q-Y", "1-6", "NumPad 1-6" },
			key = "colorKeybinds",
		},
		{
			title = "Debug Mode",
			description = "If you are encountering an issue with the plugin, use this to help us solve your problem!",
			type = "boolean",
			key = "debugMode",
		},
		-- {
		-- 	title = "Save Backup Mode",
		-- 	description = "Enable or disable saving backups of your patterns in any open place.",
		-- 	type = { "Encrypted", "Unencrypted", "None" },
		-- 	key = "saveBackupMode",
		-- },
	},
} :: settings

local defaultValues = {
	theme = "Auto",
	fakeBloom = true,
	colorKeybinds = "Q-Y",
	debugMode = false,
	saveBackupMode = "Encrypted",
}

--- Generates a save key for the plugin settings.
local function getSaveKey(key: string): string
	return `RedonTech_EVC_{key}`
end

--- Sets up the settings module, loading saved values and setting up observers for changes.
function settings:setup(scope: types.Scope)
	self.scope = scope

	local valuesToLoad = { "theme", "fakeBloom", "colorKeybinds", "debugMode", "saveBackupMode" }

	self.values = {} :: any
	for _, key in pairs(valuesToLoad) do
		local savedValue = plugin:GetSetting(getSaveKey(key))

		if savedValue ~= nil then
			self.values[key] = scope:Value(savedValue)
		else
			self.values[key] = scope:Value(defaultValues[key])
		end
	end
	log.debug("Settings loaded", self.values)

	for key, value: Fusion.Value<any> in pairs(self.values) do
		scope:Observer(value):onChange(function()
			task.spawn(function()
				local newValue = scope.peek(value)
				plugin:SetSetting(getSaveKey(key), newValue)
				log.debug("Setting saved", getSaveKey(key), newValue)
			end)
		end)
	end
	log.debug("Observers set up")
end

return settings


--# selene: allow(unused_variable)
-- Redon Tech Emergency Vehicle Creator, MIT License

local root = script.Parent.Parent

local Fusion = require(root.Packages.fusion)
local RedonUI = require(root.Packages["redon-ui"])
local utils = require(script.Parent.utils)

-- Code Types

export type Scope = Fusion.Scope<typeof(Fusion) & typeof(RedonUI.Components) & typeof(RedonUI.Util) & typeof(utils)>

export type mode = "menu" | "main" | "viewport"

export type menuPanels = "Home" | "Vehicle" | "Settings" | "Help" | "Credits" | "Permission Needed"

export type mainPanels = "Vehicle Setup" | "ELS Setup"
export type vehicleSetupPages = "Functions" | "Sirens" | "Light Settings"
export type elsSetupPages = "Flashers" | "Faders" | "Rotators"
export type pages = vehicleSetupPages | elsSetupPages

export type returnTo = "menu" | "customizer" | "vehicleSetup"

-- Settings Types

export type theme = "Auto" | "Latte" | "Frappe" | "Macchiato" | "Mocha"

export type keybinds = "Q-Y" | "1-6" | "NumPad 1-6"

export type saveBackupMode = "Encrypted" | "Unencrypted" | "None"

-- ELS Types

export type elsColor = number

export type flasherSection = {
	waitTime: number,
	rowCount: number,
	columns: { [number]: { lightName: string, rows: { elsColor } } },
}

export type flasherColumnDataState = {
	currentName: Fusion.Value<string>?,
	lightName: Fusion.Value<string>,
	rows: Fusion.Value<{ elsColor }>,
}
export type flasherSectionState = {
	waitTime: Fusion.Value<number>,
	rowCount: Fusion.Value<number>,
	nameTemplate: {
		pattern: string,
		originalPosition: number,
		originalNumber: number,
	}?,
	columns: Fusion.Value<{ [number]: flasherColumnDataState }>,
	currentRow: Fusion.Value<number>,
}

export type faderData = {
	type: "Simple",
	time: number,
	goal: number,
	color: elsColor,
} | {
	type: "Advanced",
	time: number,
	goal: number,
	color: elsColor,
	easingStyle: Enum.EasingStyle,
	easingDirection: Enum.EasingDirection,
} | {
	type: "Wait",
	time: number,
}

export type faderSection = {
	lightName: string,
	data: { faderData },
}

export type faderTweenDataState = {
	type: Fusion.Value<"Simple" | "Advanced" | "Wait">,
	time: Fusion.Value<number>,

	-- Non-Wait Specific
	goal: Fusion.Value<number>?,
	color: Fusion.Value<elsColor>?,

	-- Advanced Specific
	easingStyle: Fusion.Value<Enum.EasingStyle>?,
	easingDirection: Fusion.Value<Enum.EasingDirection>?,
}

export type faderSectionState = {
	lightName: Fusion.Value<string>,
	currentPercentage: Fusion.Value<number>,
	currentTween: Fusion.Value<number>,
	currentValue: Fusion.Value<number>,
	previousGoal: number?,
	data: Fusion.Value<{ faderTweenDataState }>,
}

export type rotatorData = {
	type: "Angle",
	color: elsColor,
	velocity: number,
	angle: number,
} | {
	type: "Infinite",
	color: elsColor,
	velocity: number,
} | {
	type: "Wait",
	time: number,
}

export type rotatorSection = {
	lightName: string,
	data: { rotatorData },
}

export type rotatorDataState = {
	type: Fusion.Value<"Angle" | "Infinite" | "Wait">,
	-- Non-Wait Specific
	velocity: Fusion.Value<number>?,
	color: Fusion.Value<elsColor>?,

	-- Angle Specific
	angle: Fusion.Value<number>?,

	-- Wait Specific
	time: Fusion.Value<number>?,
}

export type rotatorSectionState = {
	lightName: Fusion.Value<string>,
	currentPercentage: Fusion.Value<number>,
	currentAngle: Fusion.Value<number>,
	currentValue: Fusion.Value<number>,
	previousGoal: number?,
	data: Fusion.Value<{ rotatorDataState }>,
}

export type pattern = {
	flashers: { flasherSection },
	faders: { faderSection },
	rotators: { rotatorSection },
}

export type selectedPattern = {
	functionId: number,
	patternId: number,
	pattern: pattern,
}

export type activation = {
	type: "Standard",
	mode: "Cycle" | "Set Pattern",
	pattern: number,
	keybinds: { Enum.KeyCode },
} | {
	type: "Default",
	pattern: number,
} | {
	type: "Override",
	mode: "Siren Override" | "Park" | "Brake" | "Reverse",
	siren: string?,
	dependentFunction: string,
	dependentPattern: number,
} | {
	type: "Conditional",
	mode: "Always Active" | "Keybind" | "Function" | "Siren",
	conditions: {
		{
			type: "Function",
			mode: "Is" | "Is Not",
			dependentFunction: string,
			dependentPattern: number,
		}
	},
} | {
	type: "AG-Event",
	mode: "Stages" | "Traffic Advisor" | "Scene" | "Ally Left" | "Ally Right" | "Rear Scene",
}

export type activationState = {
	type: Fusion.Value<"Standard" | "Default" | "Override" | "AG-Event">,
	mode: Fusion.Value<
		"Cycle"
		| "Set Pattern"
		| "Siren Override"
		| "Park"
		| "Brake"
		| "Reverse"
		| "Always Active"
		| "Keybind"
		| "Function"
		| "Siren"
		| "Stages"
		| "Traffic Advisor"
		| "Scene"
		| "Ally Left"
		| "Ally Right"
		| "Rear Scene"
	>?,
	pattern: Fusion.Value<number>?,
	keybinds: Fusion.Value<{ Fusion.Value<Enum.KeyCode> }>?,
	siren: Fusion.Value<string?>?,
	dependentFunction: Fusion.Value<string>?,
	dependentPattern: Fusion.Value<number>?,
	conditions: Fusion.Value<{
		{
			type: Fusion.Value<"Function">,
			mode: Fusion.Value<"Is" | "Is Not">,
			dependentFunction: Fusion.Value<string>,
			dependentPattern: Fusion.Value<number>,
		}
	}>?,
}

export type elsFunction = {
	weight: number,
	name: string,
	activations: { activation },
	patterns: { pattern },
}

-- Siren Types

export type modifier = {
	name: string,
	modifiedSirenName: string,
	parentSoundBehavior: "Play Parent Sound" | "Plays Simultaneously",
	behavoir: "Play Automatically" | "Require Parent Replay",
	delay: number,
}

export type siren = {
	name: string,
	type: "Siren" | "Hold",
	behavoir: "Overrides Other Sounds" | "Plays Simultaneously",
	keybinds: { Enum.KeyCode },
	modifiers: { modifier },
} | {
	name: string,
	type: "Modifier",
	keybinds: { Enum.KeyCode },
}

return nil


-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent

local RedonUI = require(root.Packages["redon-ui"])

local utils = {}

--- Deep copies a table, including nested tables.
function utils.deepCopy<T>(inputTable: T): T
	if typeof(inputTable) ~= "table" then
		return inputTable
	end

	local function copy(input: { any })
		local output = {}
		for key, value in pairs(input) do
			if typeof(value) == "table" then
				output[key] = copy(value)
			else
				output[key] = value
			end
		end
		return output
	end

	return copy(inputTable)
end

--- Takes a table and swaps the values at the specified indices.
function utils.arraySwap(array: { [number]: unknown }, index1: number, index2: number)
	local value1 = array[index1]
	local value2 = array[index2]
	array[index1] = value2
	array[index2] = value1
end

--- Inserts a value into an array at the specified index or at the end if the index is greater than the array length.
function utils.insertAtNumberOrEnd(array: { [number]: unknown }, index: number, value: unknown): number
	if index > #array then
		table.insert(array, value)
		return #array
	else
		table.insert(array, index, value)
		return index
	end
end

--- Converts an array to a dictionary with the values as keys and their indices as values.
function utils.arrayIndexToKey<I, V>(array: { [I]: V }): { [V]: I }
	local output = {}
	for index, value in array do
		output[value] = index
	end
	return output
end

--- Converts a dictionary of values to an array, using a specified key from the value as the index.
function utils.dictionaryValueToIndex<I, V, K>(dictionary: { [I]: V }, valueIndex: K): { [unknown]: V }
	local output = {}
	for _, value in dictionary do
		if typeof(value) ~= "table" then
			continue
		end
		output[value[valueIndex]] = value
	end
	return output
end

--- Compares two tables for equality, including nested tables.
function utils.compareTables(table1: { [any]: any }, table2: { [any]: any }): boolean
	for index, value in table1 do
		if table2[index] == nil then
			return false
		end

		if typeof(value) == "table" then
			if utils.compareTables(value, table2[index]) == false then
				return false
			end
		else
			if table2[index] ~= value then
				return false
			end
		end
	end
	for index, value in table2 do
		if table1[index] == nil then
			return false
		end

		if typeof(value) == "table" then
			if utils.compareTables(value, table1[index]) == false then
				return false
			end
		else
			if table1[index] ~= value then
				return false
			end
		end
	end
	return true
end

--- Gets a color from the current theme by its name.
--- Converts "purple" to "mauve".
function utils.getColorByName(name: string, default: Color3): Color3
	local currentTheme = RedonUI.theme.theme:now()

	if name == "Purple" then
		name = "Mauve"
	end
	local color = currentTheme.colors[name:lower()]
	if color == nil then
		return default
	end
	return color
end

utils.bloomColors = {
	Blue = Color3.fromRGB(17, 99, 207),
	Red = Color3.fromRGB(255, 61, 61),
	Yellow = Color3.fromRGB(255, 230, 43),
	White = Color3.fromRGB(255, 255, 255),
	Green = Color3.fromRGB(94, 225, 110),
	Purple = Color3.fromRGB(246, 114, 255),
}

return utils


-- Redon Tech Emergency Vehicle Creator, MIT License

local ChangeHistoryService = game:GetService("ChangeHistoryService")
local assets = require(script.Parent.assets)
local log = require(script.Parent.log)

export type option = {
	type: "select" | "single",
	default: string | boolean,
	canSelectMultiple: boolean?,
	canHaveNone: boolean?,
	icon: string?,
	iconOnly: boolean?,
	layoutOrder: number,
	choices: { string }?,
}
export type equipmentData = {
	default: boolean,
	hasLights: boolean,
	credit: string,
	icon: string?,
	layoutOrder: number,
	configuration: {
		[string]: option,
	},
}
type vehicleData = {
	customization: {
		[string]: { -- Location: data
			[string]: equipmentData, -- Equipment Name: data
		},
	},
}
export type vehicleFolder = {
	vehicle: Model,
	customization: Model,
	data: vehicleData,
}
type vehicle = {
	vehicles: {
		[string]: {
			icon: string,
			outfitter: string,
			credits: string,
			order: number,
			assetId: number,
		},
	},
	cache: { [string]: vehicleFolder },
	isValidVehicle: (self: vehicle, vehicle: Instance) -> false | "a" | "ag" | "mg" | "m",
	isVehicleCustomizable: (self: vehicle, vehicle: Instance) -> boolean,
	getOutfitting: (self: vehicle, vehicle: Instance) -> { model: Model, basePoint: BasePart }?,
	_setupVehicle: (self: vehicle, vehicleName: string, vehicleModule: any, vehicleFolder: Folder) -> vehicleFolder?,
	getVehicle: (self: vehicle, vehicleName: string) -> vehicleFolder?,
	initVehicle: (self: vehicle, vehicleModel: Model) -> (),
	changeConfiguration: (
		self: vehicle,
		vehicleFolder: vehicleFolder,
		equipmentModel: Model,
		configuration: { [string]: boolean | { [string]: boolean } }
	) -> (),
	updateEquipmentLights: (self: vehicle, equipmentModel: Model) -> (),
	changeEquipment: (
		self: vehicle,
		vehicleFolder: vehicleFolder,
		outfitting: Model,
		location: string,
		equipmentName: string?
	) -> boolean,
	cloneCustomizationBasePoint: (self: vehicle, vehicleFolder: vehicleFolder) -> BasePart?,
}
local vehicle = {
	vehicles = {
		["2020 FPIU"] = {
			icon = assets.images.vehicles.FPIU2020,
			outfitter = "RetiredSubaru",
			credits = "EonWare/LT_haywood",
			order = 1,
			assetId = 95348918634521,
		},
		["2018 Dodge Charger Pursuit"] = {
			icon = assets.images.vehicles.DODGECHARGER2018,
			outfitter = "MiataVxbe",
			credits = "EonWare/LT_haywood",
			order = 2,
			assetId = 90780122255551,
		},
	},
	cache = {},
} :: vehicle

--- Checks if the given instance is a valid vehicle model.
function vehicle.isValidVehicle(_, vehicleToCheck)
	if vehicleToCheck:IsA("Model") then
		local tune = vehicleToCheck:FindFirstChild("A-Chassis Tune")
		if tune then
			-- If plugins doesn't exist, it can not be used
			local plugins = tune:FindFirstChild("Plugins")
			if plugins == nil then
				return false
			end

			-- Detect AG chassis
			local loader = tune:FindFirstChild("AG-Chassis [Loader]")
			if loader then
				return "ag"
			end

			return "a"
		end

		-- Detect MG chassis
		tune = vehicleToCheck:FindFirstChild("MG-Chassis Tune")
		if tune then
			return "mg"
		end

		-- Detect NVNA Constraint Type: Motorcycle
		-- AKA Motorcycle Chassis
		tune = vehicleToCheck:FindFirstChild("Tuner")
		if tune then
			return "m"
		end
	end
	return false
end

--- Determines if a vehicle is customizable by the plugin.
function vehicle.isVehicleCustomizable(self, vehicleToCheck)
	if vehicleToCheck:GetAttribute("EVCHandled") == true and self:getOutfitting(vehicleToCheck) ~= nil then
		return true
	end
	return false
end

--- Retrieves the outfitting model and base point for a vehicle.
function vehicle.getOutfitting(_, vehicleToCheck)
	local carBody = vehicleToCheck:FindFirstChild("Body")
	if carBody == nil then
		return
	end
	local carOutfitting = carBody:FindFirstChild("Outfitting") :: Model?
	if carOutfitting == nil or carOutfitting:IsA("Model") == false then
		return
	end
	local carBasePoint = carOutfitting:FindFirstChild("BasePoint") :: BasePart?
	if carBasePoint == nil or carBasePoint:IsA("BasePart") == false then
		return
	end

	return {
		model = carOutfitting,
		basePoint = carBasePoint,
	}
end

--- Sets up the vehicle by loading its model, customization, and configuration data.
function vehicle._setupVehicle(self, vehicleName, vehicleModule, vehicleFolder)
	if self.cache[vehicleName] ~= nil then
		return
	end

	-- Check and get the vehicle model
	local vehicleModel = vehicleFolder:FindFirstChild("Vehicle") :: Model?
	if vehicleModel == nil or vehicleModel:IsA("Model") == false then
		return
	end
	-- vehicleModel:MoveTo(Vector3.new(0, 0, 0))
	vehicleModel:SetAttribute("EVCHandled", true)
	vehicleModel:SetAttribute("EVCVehicleName", vehicleName)

	-- Check and get the customization model
	local customization = vehicleFolder:FindFirstChild("Customization") :: Model?
	if customization == nil or customization:IsA("Model") == false then
		return
	end
	-- customization:MoveTo(Vector3.new(0, 0, 0))
	local BasePoint = customization:FindFirstChild("BasePoint") :: BasePart?
	if BasePoint == nil or BasePoint:IsA("BasePart") == false then
		return
	end

	-- Check and get the EVC_Config module
	-- local EVC_Config = vehicleFolder:FindFirstChild("EVC_Config") :: ModuleScript?
	-- if EVC_Config == nil or EVC_Config:IsA("ModuleScript") == false then
	-- 	return
	-- end
	-- local data = (require :: any)(EVC_Config) :: vehicleData
	local data = vehicleModule

	-- Update everything to ensure compliance
	for _, v in pairs(customization:GetDescendants()) do
		if v:IsA("Model") then
			-- Set the primary part
			for _, model in pairs(v:GetChildren()) do
				if model:IsA("Model") then
					local point = model:FindFirstChild("Point") :: BasePart?
					if point == nil or point:IsA("BasePart") == false then
						continue
					end
					point:SetAttribute("originalTransparency", point.Transparency)
					model.PrimaryPart = point
				end
			end
		elseif v:IsA("BasePart") and v.Parent and v.Parent.Name == "_LIGHTS" then
			v:SetAttribute("originalName", v.Name)
		end
	end

	for _, v in pairs(customization:GetChildren()) do
		if v:IsA("Model") then
			-- Remove spaces from model names
			local location = data.customization[v.Name]
			if location == nil then
				log.warn("Failed to find location data for", vehicleName, v.Name)
				continue
			end
			local attachment = BasePoint:FindFirstChild(v.Name)
			data.customization[v.Name] = nil
			v.Name = v.Name:gsub(" ", "_")
			data.customization[v.Name] = location
			log.debug("Updated location model name", vehicleName, v.Name)
			if attachment then
				attachment.Name = v.Name
			end

			-- Loop through the equipments configurations and set their choices
			-- and name
			for equipmentName, equipmentData in pairs(location) do
				if next(equipmentData.configuration) == nil then
					continue
				end
				local equipmentModel = v:FindFirstChild(equipmentName) :: Model?
				if equipmentModel == nil or equipmentModel:IsA("Model") == false then
					log.warn("Failed to find equipment model for", vehicleName, v.Name, equipmentName)
					continue
				end
				local options = equipmentModel:FindFirstChild("Options") :: Model?
				if options == nil or options:IsA("Model") == false then
					log.warn("Failed to find options model for", vehicleName, v.Name, equipmentName)
					continue
				end

				for configurationName, configurationData in pairs(equipmentData.configuration) do
					local configurationModel = options:FindFirstChild(configurationName)
					if configurationModel == nil then
						log.warn(
							"Failed to find configuration instance for",
							vehicleName,
							v.Name,
							equipmentName,
							configurationName
						)
						continue
					end
					configurationModel.Name = configurationName:gsub(" ", "_")
					equipmentData.configuration[configurationName] = nil
					equipmentData.configuration[configurationModel.Name] = configurationData

					if configurationData.type == "select" then
						configurationData.choices = {}
						for _, option in pairs(configurationModel:GetChildren()) do
							table.insert(configurationData.choices :: {}, option.Name)
						end
						table.sort(configurationData.choices :: { string })
					end
				end
			end
		end
	end
	log.debug(data)

	-- Freeze the data, so it can't be modified
	if table.isfrozen(data) == false then
		table.freeze(data)
	end
	-- Create the vehicle data
	local vehicleData = {
		vehicle = vehicleModel,
		customization = customization,
		data = data,
	}
	self.cache[vehicleName] = vehicleData
	log.debug("Loaded vehicle", vehicleName)
	return vehicleData
end

--- Loads a vehicle by its name, checking the cache first.
function vehicle.getVehicle(self, vehicleName)
	log.debug("Loading vehicle", vehicleName)
	if self.cache[vehicleName] ~= nil then
		local vehicleData = self.cache[vehicleName]
		log.debug("Vehicle already loaded", vehicleName, vehicleData)
		return vehicleData
	end

	if self.vehicles[vehicleName] then
		local success, vehicleModule = pcall(function()
			-- return InsertService:LoadAsset(self.vehicles[vehicleName].assetId)
			log.debug("Loading vehicle", vehicleName, self.vehicles[vehicleName].assetId)
			if log.verbosity >= log.level.debug then
				local inWorldFolder = workspace:FindFirstChild(vehicleName) :: Folder?
				if inWorldFolder ~= nil and inWorldFolder:IsA("Folder") then
					local MainModule = inWorldFolder:FindFirstChild("MainModule") :: ModuleScript?
					if MainModule ~= nil and MainModule:IsA("ModuleScript") then
						log.warn("Using MainModule found in workspace", vehicleName, MainModule)
						return (require :: any)(MainModule)
					end
				end
			end
			return (require :: any)(self.vehicles[vehicleName].assetId)
			-- return (require :: any)(workspace["2020FPIU"].MainModule)
		end)

		if success then
			local vehicleFolder = Instance.new("Folder")
			vehicleFolder.Name = vehicleName
			vehicleFolder.Parent = script:FindFirstAncestorOfClass("Plugin")

			-- local vehicleModel, customizationModel = vehicleModule.getModels()
			-- vehicleModel.Parent = vehicleFolder
			-- customizationModel.Parent = vehicleFolder
			vehicleModule.vehicleModel:Clone().Parent = vehicleFolder
			vehicleModule.customizationModel:Clone().Parent = vehicleFolder
			-- vehicleFolder = vehicleFolder:FindFirstChildWhichIsA("Folder")
			-- if vehicleFolder == nil then
			-- 	log.warn("Failed to load vehicle", vehicleName, vehicleFolder)
			-- 	return
			-- end
			log.debug("Inserted", vehicleFolder, vehicleFolder:GetDescendants())
			-- vehicleFolder.Parent = script:FindFirstAncestorOfClass("Plugin")

			return self:_setupVehicle(vehicleName, vehicleModule, vehicleFolder)
		end
		log.warn("Failed to load vehicle", vehicleName, vehicleModule)
	end

	return
end

--- Shows the model by setting its transparency to 0.
local function showModel(path: Instance)
	for _, v in pairs(path:GetDescendants()) do
		if v:IsA("BasePart") and (v.Parent and v.Parent.Name ~= "_LIGHTS") then
			local originalTransparency = v:GetAttribute("originalTransparency")
			if originalTransparency ~= nil and type(originalTransparency) == "number" then
				v.Transparency = originalTransparency
			else
				v.Transparency = 0
			end
		end
	end

	if path:IsA("BasePart") then
		local originalTransparency = path:GetAttribute("originalTransparency")
		if originalTransparency ~= nil and type(originalTransparency) == "number" then
			path.Transparency = originalTransparency
		else
			path.Transparency = 0
		end
	end
end

--- Retrieves the original configuration for a given equipment model.
local function getOriginalConfiguration(
	vehicleFolder: vehicleFolder,
	basePoint: BasePart,
	equipmentModel: Model,
	configurationName: string
): Instance?
	local originalLocationName = equipmentModel:GetAttribute("EVCEquipmentName") :: string?
	if originalLocationName == nil or typeof(originalLocationName) ~= "string" then
		log.warn("Failed to get original location name", originalLocationName)
		return
	end
	local originalLocation = vehicleFolder.customization:FindFirstChild(originalLocationName) :: Model?
	if originalLocation == nil or originalLocation:IsA("Model") == false then
		log.warn("Failed to get original locatio", originalLocationName)
		return
	end
	local originalEquipmentName = equipmentModel.Name
	local originalEquipment = originalLocation:FindFirstChild(originalEquipmentName) :: Model?
	if originalEquipment == nil or originalEquipment:IsA("Model") == false then
		log.warn("Failed to get original equipment", originalLocationName)
		return
	end
	local customizationPrimaryPart = originalEquipment.PrimaryPart
	if customizationPrimaryPart == nil then
		return
	end

	local newEquipment = originalEquipment:Clone()
	newEquipment:SetAttribute("EVCEquipmentName", originalLocationName)
	newEquipment:PivotTo(basePoint.CFrame)
	newEquipment.Parent = equipmentModel.Parent

	local originalOptions = newEquipment:FindFirstChild("Options") :: Model?
	if originalOptions == nil or originalOptions:IsA("Model") == false then
		log.warn("Failed to get original options", originalLocationName)
		return
	end
	local originalConfiguration = originalOptions:FindFirstChild(configurationName)
	if originalConfiguration == nil then
		log.warn("Failed to get configuration", originalLocationName)
		return
	end

	local new = originalConfiguration:Clone()
	new.Parent = plugin
	newEquipment:Destroy()
	return new
end

--- Changes the configuration of a vehicle's equipment.
function vehicle.changeConfiguration(_, vehicleFolder, equipmentModel, configuration)
	local options = equipmentModel:FindFirstChild("Options") :: Model?
	if options == nil or options:IsA("Model") == false then
		log.warn("Failed to find options", equipmentModel)
		return
	end
	if equipmentModel.Parent == nil then
		log.warn("Failed to find equipment parent", equipmentModel)
		return
	end
	local basePoint = equipmentModel.Parent:FindFirstChild("BasePoint") :: BasePart?
	if basePoint == nil or basePoint:IsA("BasePart") == false then
		log.warn("Failed to find base point", equipmentModel)
		return
	end

	for configurationName, configurationData in pairs(configuration) do
		local optionModel = options:FindFirstChild(configurationName)
		local originalConfiguration =
			getOriginalConfiguration(vehicleFolder, basePoint, equipmentModel, configurationName)
		if originalConfiguration == nil then
			log.warn("Failed to find original configuration", configurationName)
			continue
		end

		if configurationData == true then
			log.debug("Activate configuration", configurationName, configurationData)
			local model = optionModel or originalConfiguration
			showModel(model)
			model.Parent = options
			if model ~= originalConfiguration then
				originalConfiguration:Destroy()
			end
			equipmentModel:SetAttribute(configurationName, configurationData)
		elseif typeof(configurationData) == "table" and optionModel ~= nil then
			log.debug("Changing configuration", configurationName, configurationData, optionModel)
			optionModel:ClearAllChildren()
			local attribute = ""
			for name, value in configurationData do
				local model = originalConfiguration:FindFirstChild(name)
				if model ~= nil and value == true then
					showModel(model)
					model.Parent = optionModel
					attribute ..= `{name}+`
				end
			end
			originalConfiguration:Destroy()
			equipmentModel:SetAttribute(configurationName, attribute)
		elseif optionModel ~= nil then
			log.debug("Deleting configuration", configurationName, configurationData)
			optionModel:Destroy()
			originalConfiguration:Destroy()
			equipmentModel:SetAttribute(configurationName, configurationData)
		end
		vehicle:updateEquipmentLights(equipmentModel)
	end
	ChangeHistoryService:SetWaypoint("Changed equipment configuration")
end

--- Changes the equipment for a vehicle at a specific location.
function vehicle.changeEquipment(self, vehicleFolder, outfitting, location, equipmentName)
	local basePoint = outfitting:FindFirstChild("BasePoint") :: BasePart?
	if basePoint == nil or basePoint:IsA("BasePart") == false then
		log.warn("Failed to find base point", outfitting)
		return false
	end

	local currentEquipment = outfitting:GetAttribute(location)
	if currentEquipment == equipmentName then
		log.warn("Equipment is already equipped", currentEquipment, equipmentName)
		return false
	end

	-- Clear the current customization
	if currentEquipment ~= nil and typeof(currentEquipment) == "string" and currentEquipment ~= "BasePoint" then
		local currentModel = outfitting:FindFirstChild(currentEquipment) :: Model?
		if currentModel == nil then
			log.warn("Failed to find current equipment", currentEquipment)
			return false
		end
		currentModel:Destroy()
		outfitting:SetAttribute(location, nil)
	end

	if equipmentName == nil then
		return true
	end
	local locationModel = vehicleFolder.customization:FindFirstChild(location) :: Model?
	if locationModel == nil or locationModel:IsA("Model") == false then
		log.warn("Failed to find local location model", location)
		return false
	end
	local newModel = locationModel:FindFirstChild(equipmentName) :: Model?
	if newModel == nil or newModel:IsA("Model") == false then
		log.warn("Failed to find new equipment model", equipmentName)
		return false
	end
	local customizationPrimaryPart = newModel.PrimaryPart
	if customizationPrimaryPart == nil then
		log.warn("Failed to find primary part", newModel)
		return false
	end

	local newEquipment = newModel:Clone()
	newEquipment:SetAttribute("EVCEquipmentName", location)
	newEquipment:PivotTo(basePoint.CFrame)
	newEquipment.Parent = outfitting
	outfitting:SetAttribute(location, equipmentName)
	showModel(newEquipment)

	local data = vehicleFolder.data.customization[location][equipmentName]
	if next(data.configuration) == nil then
		log.debug("No configuration found", location, equipmentName)
		return true
	end
	local defaultConfiguration: { [string]: boolean | { [string]: boolean } } = {}
	for optionName, option in pairs(data.configuration) do
		if option.type == "select" and option.choices then
			local default = {}
			for _, choice in option.choices do
				if choice == option.default then
					default[choice] = true
				else
					default[choice] = false
				end
			end
			defaultConfiguration[optionName] = default
		elseif typeof(option.default) == "boolean" then
			defaultConfiguration[optionName] = option.default :: boolean
		end
	end
	self:changeConfiguration(vehicleFolder, newEquipment, defaultConfiguration)
	return true
end

--- Initializes a vehicle by applying its default outfitting and equipment.
function vehicle.initVehicle(self, vehicleModel)
	if self.vehicles[vehicleModel.Name] then
		local vehicleFolder = self:getVehicle(vehicleModel.Name)
		if vehicleFolder == nil then
			log.warn("Failed to outfit vehicle, no vehicle found", vehicleModel.Name)
			return
		end

		local outfitting = self:getOutfitting(vehicleModel)
		if outfitting == nil then
			log.warn("Failed to outfit vehicle, no outfitting found", vehicleModel.Name)
			return
		end

		for locationName, locationData in pairs(vehicleFolder.data.customization) do
			for equipmentName, equipmentData in pairs(locationData) do
				if equipmentData.default == true then
					self:changeEquipment(vehicleFolder, outfitting.model, locationName, equipmentName)
				end
			end
		end
	end
end

--- Clones the customization base point from the vehicle folder to the current camera.
function vehicle.cloneCustomizationBasePoint(_, vehicleFolder)
	local basePoint = vehicleFolder.customization:FindFirstChild("BasePoint") :: BasePart?
	if basePoint == nil or basePoint:IsA("BasePart") == false then
		return
	end

	local customizationBasePoint = basePoint:Clone()
	customizationBasePoint.Parent = workspace.CurrentCamera
	return customizationBasePoint
end

--- Updates the names of the equipment lights based on the light prefix attribute.
function vehicle.updateEquipmentLights(_, equipmentModel)
	local lightPrefix = equipmentModel:GetAttribute("lightPrefix") :: string?
	if lightPrefix == nil or typeof(lightPrefix) ~= "string" then
		return
	end

	for _, v in pairs(equipmentModel:GetDescendants()) do
		if v.Parent and v.Parent.Name == "_LIGHTS" then
			local originalName = v:GetAttribute("originalName")
			if originalName ~= nil and typeof(originalName) == "string" then
				v.Name = lightPrefix .. originalName
			else
				v.Name = lightPrefix .. v.Name
			end
		end
	end
	ChangeHistoryService:SetWaypoint("Updated equipment lights")
end

return vehicle