/**
 * @fileoverview Marketplace listing data structures and types.
 *
 * This module defines the core data structures used in the marketplace system
 * for managing item listings, auctions, and trade information.
 *
 * @since 1.0.0
 */

import { DataType } from "@rbxts/flamework-binary-serializer";

declare global {
    /**
     * Represents a marketplace listing for a unique item.
     */
    interface MarketplaceListing {
        /**
         * The UUID of the unique item being sold.
         */
        uuid: string;

        /**
         * The empire ID where the item is being sold from.
         */
        sellerEmpireId: string;

        /**
         * The asking price for the item.
         */
        price: number;

        /**
         * Unix timestamp when the listing was created.
         */
        created: DataType.f64;

        /**
         * Unique item instance data associated with this listing, including raw pot values.
         */
        uniqueItem: DataType.Packed<UniqueItemInstance>;

        /**
         * The empire ID of the buyer that is in the process of buying this listing (if any).
         */
        lock?: string;

        /**
         * Unix timestamp when the listing was locked for purchase (if any).
         */
        lockedAt?: DataType.f64;

        /**
         * Whether the item has been bought and finalized.
         */
        bought: boolean;
    }

    /**
     * External trade token for crash recovery.
     */
    interface TradeToken {
        /**
         * The UUID of the item being traded.
         */
        uuid: string;

        /**
         * The empire ID of the buyer.
         */
        buyerEmpireId: string;

        /**
         * The empire ID of the seller.
         */
        sellerEmpireId: string;

        /**
         * The agreed upon price.
         */
        price: number;

        /**
         * Unix timestamp when the trade was initiated.
         */
        timestamp: DataType.f64;

        /**
         * Current status of the trade.
         */
        status: "processing" | "completed" | "failed" | "rolled_back";
    }

    /**
     * Marketplace transaction history entry.
     */
    interface MarketplaceTransaction {
        /**
         * The UUID of the item that was traded.
         */
        uuid: string;

        /**
         * The base item ID for reference.
         */
        baseItemId: string;

        /**
         * The empire ID of the buyer.
         */
        buyerEmpireId: string;

        /**
         * The empire ID of the seller.
         */
        sellerEmpireId: string;

        /**
         * The final sale price.
         */
        price: number;

        /**
         * Unix timestamp of the transaction.
         */
        timestamp: DataType.f64;
    }
}

/**
 * Marketplace configuration constants.
 */
const MARKETPLACE_CONFIG = {
    /** Whether the marketplace is enabled */
    ENABLED: true,

    /** Time in seconds before a listing lock expires */
    LOCK_TIMEOUT: 5 * 60, // 5 minutes

    /** Maximum number of active listings per empire */
    MAX_LISTINGS_PER_EMPIRE: 5,

    /** Default listing duration in seconds (7 days) */
    LISTING_DURATION: 7 * 24 * 60 * 60,

    /** Transaction tax percentage (2%) */
    TRANSACTION_TAX_PERCENTAGE: 0.02,

    /** Maximum price for any listing */
    MAX_LISTING_PRICE: 1e12,

    /** Minimum price for any listing */
    MIN_LISTING_PRICE: 1,

    /**
     * DataStore where actual marketplace listings are stored.
     * Keys: {@link MarketplaceListing.uuid}
     * Values: {@link MarketplaceListing}
     */
    DATASTORE_NAME: "MarketplaceListings",

    /**
     * DataStore where listings can be searched.
     * Keys: 0-indexed integers
     * Values: {@link MarketplaceListing[]} (array of listings)
     */
    INDEX_DATASTORE_NAME: "MarketplaceIndex",

    /**
     * DataStore where transaction history of unique instances are stored.
     * Keys: {@link MarketplaceTransaction.uuid}
     * Values: {@link MarketplaceTransaction[]} (array of transactions)
     */
    HISTORY_DATASTORE_NAME: "MarketplaceHistory",
} as const;

export = MARKETPLACE_CONFIG;


import { TweenService } from "@rbxts/services";
import WorldNode from "shared/world/nodes/WorldNode";

class BuildGrid extends WorldNode {
    readonly TRANSPARENCY_TWEENINFO = new TweenInfo(0.3, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out);

    constructor() {
        super(script.Name);
    }

    setTransparency(transparency: number) {
        for (const grid of this.INSTANCES) {
            const texture = grid.FindFirstChildOfClass("Texture");
            if (texture === undefined) continue;
            TweenService.Create(texture, this.TRANSPARENCY_TWEENINFO, { Transparency: transparency }).Play();
        }
    }
}

export = new BuildGrid();


import { Workspace } from "@rbxts/services";
import eat from "shared/hamster/eat";
import Sandbox from "shared/Sandbox";
import { SingleWorldNode } from "shared/world/nodes/WorldNode";

class EmpireIdOverrideValue extends SingleWorldNode<StringValue> {
    constructor(tag: string) {
        super(tag);
        if (Sandbox.getEnabled()) {
            const instance = new Instance("StringValue");
            instance.AddTag(tag);
            instance.Value = Sandbox.getId() ?? "SANDBOX";
            instance.Parent = Workspace;
            eat(instance, "Destroy");
        }
    }
}

export = new EmpireIdOverrideValue(script.Name);


import { CollectionService } from "@rbxts/services";
import eat from "shared/hamster/eat";

export default class WorldNode<T extends Instance = Instance> {
    readonly INSTANCES = new Set<T>();
    private addedConnection?: RBXScriptConnection;
    private removedConnection?: RBXScriptConnection;

    constructor(
        public tag: string,
        private onRegister?: (instance: T) => void,
        private onUnregister?: (instance: T) => void,
    ) {
        for (const instance of CollectionService.GetTagged(tag)) {
            this.registerInstance(instance as T);
        }
        this.addedConnection = CollectionService.GetInstanceAddedSignal(tag).Connect((instance) => {
            this.registerInstance(instance as T);
        });
        this.removedConnection = CollectionService.GetInstanceRemovedSignal(tag).Connect((instance) => {
            this.unregisterInstance(instance as T);
        });
        eat(() => this.cleanup());
    }

    private registerInstance(instance: T) {
        this.INSTANCES.add(instance);
        this.onRegister?.(instance);
        eat(
            instance.AncestryChanged.Connect(() => {
                if (instance.Parent === undefined) {
                    this.unregisterInstance(instance);
                }
            }),
        );
    }

    private unregisterInstance(instance: T) {
        this.INSTANCES.delete(instance);
        this.onUnregister?.(instance);
    }

    cleanup() {
        for (const instance of this.INSTANCES) {
            this.unregisterInstance(instance);
        }
        this.addedConnection?.Disconnect();
        this.removedConnection?.Disconnect();
    }
}

export class SingleWorldNode<T extends Instance = Instance> extends WorldNode<T> {
    originalParent?: Instance;

    constructor(tag: string, onRegister?: (instance: T) => void, onUnregister?: (instance: T) => void) {
        super(
            tag,
            (instance) => {
                this.originalParent = instance.Parent;
                onRegister?.(instance);
            },
            onUnregister,
        );
    }

    /**
     * Get the single instance registered to this node.
     * @returns The registered instance, or undefined if none exists.
     */
    getInstance(): T | undefined {
        for (const instance of this.INSTANCES) {
            return instance;
        }
        return undefined;
    }

    /**
     * Wait for the instance to be registered to this node.
     * @returns The registered instance.
     */
    waitForInstance(timeout?: number): T {
        let instance = this.getInstance();
        if (instance !== undefined) return instance;

        const startTime = os.clock();
        let warned = false;
        let exitEarly = false;
        eat(() => {
            exitEarly = true;
        });

        while (instance === undefined) {
            if (exitEarly) throw `Exited early while waiting for instance ${this.tag}`;

            task.wait();
            instance = this.getInstance();
            if (instance !== undefined) return instance;

            const elapsed = os.clock() - startTime;
            if (timeout) {
                if (elapsed > timeout) {
                    throw `Timed out waiting for instance with tag ${this.tag}`;
                }
            } else {
                if (elapsed > 10 && !warned) {
                    warn(`Waiting for instance with tag ${this.tag}...`);
                    warned = true;
                }
            }
        }

        return instance;
    }
}


import { ReplicatedFirst, StarterGui } from "@rbxts/services";

const loadingScreenModule = require(
    script.Parent!.WaitForChild("LoadingScreen") as ModuleScript,
) as typeof import("./LoadingScreen");
const LoadingScreen = loadingScreenModule.default;

LoadingScreen.showLoadingScreen("", true);
ReplicatedFirst.RemoveDefaultLoadingScreen();

StarterGui.SetCoreGuiEnabled("Backpack", false);
StarterGui.SetCoreGuiEnabled("PlayerList", false);


import { Config } from "@rbxts/jest";

export = {
    ci: false,
    clearMocks: true,
    maxWorkers: 1,
    verbose: false,
    testMatch: ["**/*.spec"],
    setupFilesAfterEnv: [
        script.Parent!.WaitForChild("setupRbxts") as ModuleScript,
        script.Parent!.WaitForChild("setup") as ModuleScript,
    ],
} satisfies Config;


import { getAllInstanceInfo } from "@antivivi/vrldk";
import { expect } from "@rbxts/jest-globals";
import { HttpService, Workspace } from "@rbxts/services";
import { Server } from "shared/api/APIExpose";
import { PLACED_ITEMS_FOLDER } from "shared/constants";
import Droplet from "shared/item/Droplet";
import Item from "shared/item/Item";
import Condenser from "shared/item/traits/dropper/Condenser";
import Items from "shared/items/Items";

export type TouchHandle = {
    part: BasePart;
    touch: (droplet: BasePart, dropletInfo: InstanceInfo) => void;
};

export type SpawnedModel = {
    item: Item;
    model: Model;
    modelInfo: InstanceInfo;
    placementId: string;
    cleanup: () => void;
};

export type SpawnedDroplet = {
    droplet: BasePart;
    dropletInfo: InstanceInfo;
    cleanup: () => void;
};

export function withWeatherDisabled<T>(callback: () => T) {
    const revenue = Server.Revenue;
    const previous = revenue.weatherBoostEnabled;
    revenue.weatherBoostEnabled = false;
    try {
        return callback();
    } finally {
        revenue.weatherBoostEnabled = previous;
    }
}

export function spawnItemModel(itemId: string): SpawnedModel {
    const item = Items.getItem(itemId);
    expect(item === undefined).toBe(false);

    const placementId = `${itemId}_${HttpService.GenerateGUID(false)}`;
    const placedItem: PlacedItem = {
        item: item!.id,
        posX: 0,
        posY: 0,
        posZ: 0,
        rotX: 0,
        rotY: 0,
        rotZ: 0,
        area: "BarrenIslands",
    };

    const model = item!.createModel(placedItem);
    expect(model === undefined).toBe(false);

    model!.Name = placementId;
    model!.Parent = PLACED_ITEMS_FOLDER;
    const modelInfo = getAllInstanceInfo(model!);
    modelInfo.maintained = true;
    Server.Item.modelPerPlacementId.set(placementId, model!);

    item!.load(model!);

    return {
        item: item!,
        model: model!,
        modelInfo,
        placementId,
        cleanup: () => {
            model!.Destroy();
            Server.Item.modelPerPlacementId.delete(placementId);
        },
    };
}

export function spawnDroplet(template: Droplet): SpawnedDroplet {
    const dropperModel = new Instance("Model") as Model;
    dropperModel.Name = `TestDropper_${HttpService.GenerateGUID(false)}`;
    dropperModel.Parent = PLACED_ITEMS_FOLDER;
    const dropperInfo = getAllInstanceInfo(dropperModel);
    dropperInfo.itemId = "TestDropper";

    const instantiator = template.getInstantiator(dropperModel);
    const droplet = instantiator() as BasePart;
    droplet.Parent = Workspace;

    const dropletInfo = Droplet.SPAWNED_DROPLETS.get(droplet);
    expect(dropletInfo).toBeDefined();
    dropletInfo!.areaId = "BarrenIslands";

    return {
        droplet,
        dropletInfo: dropletInfo!,
        cleanup: () => {
            droplet.Destroy();
            dropperModel.Destroy();
        },
    };
}

export function getTouchByTag(model: Model, tagName: string): TouchHandle {
    for (const descendant of model.GetDescendants()) {
        if (!descendant.IsA("BasePart") || !descendant.HasTag(tagName)) continue;
        const info = getAllInstanceInfo(descendant);
        const dropletTouched = info.dropletTouched;
        expect(dropletTouched).toBeDefined();
        return {
            part: descendant,
            touch: dropletTouched!,
        };
    }
    throw `No part with tag ${tagName} found in model ${model.Name}`;
}

export function getTouchByName(model: Model, partName: string): TouchHandle {
    for (const descendant of model.GetDescendants()) {
        if (!descendant.IsA("BasePart") || descendant.Name !== partName) continue;
        const info = getAllInstanceInfo(descendant);
        const dropletTouched = info.dropletTouched;
        expect(dropletTouched).toBeDefined();

        return {
            part: descendant,
            touch: dropletTouched!,
        };
    }
    throw `Part ${partName} not found in model ${model.Name}`;
}

export function setupTestCondenser() {
    const itemId = `TestCondenser_${HttpService.GenerateGUID(false)}`;
    const item = new Item(itemId).addPlaceableArea("BarrenIslands");
    const condenser = item.trait(Condenser).setQuota(1);
    const droplet = Droplet.FundsCompactDroplet;
    condenser.addDroplets(droplet);

    const placementId = `${itemId}_${HttpService.GenerateGUID(false)}`;
    const placedItem: PlacedItem = {
        item: itemId,
        posX: 0,
        posY: 0,
        posZ: 0,
        rotX: 0,
        rotY: 0,
        rotZ: 0,
        area: "BarrenIslands",
    };

    Server.empireData.items.worldPlaced.set(placementId, placedItem);

    const model = new Instance("Model") as Model;
    model.Name = placementId;
    model.Parent = PLACED_ITEMS_FOLDER;

    const drop = new Instance("Part") as BasePart;
    drop.Name = "Drop";
    drop.Size = new Vector3(1, 1, 1);
    drop.Anchored = true;
    drop.Parent = model;
    model.PrimaryPart = drop;

    Server.Item.modelPerPlacementId.set(placementId, model);

    const info = getAllInstanceInfo(model);
    info.maintained = true;

    item.load(model);

    const furnaceProcessed = getAllInstanceInfo(model).furnaceProcessed;
    expect(furnaceProcessed).toBeDefined();

    return {
        item,
        placementId,
        model,
        droplet,
        furnaceProcessed: furnaceProcessed!,
        cleanup: () => {
            model.Destroy();
            Server.Item.modelPerPlacementId.delete(placementId);
            Server.empireData.items.worldPlaced.delete(placementId);
        },
    };
}


--[[

	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.

--]]

return


local prefix, reverse, direction, detectOtherSize, otherSidePrefix, otherDifference = "", true, "X", false, "L", 0.2
local existingCount = 0

local parts = game.Selection:Get()
if #parts == 0 then
	return
end
for _, v in pairs(parts) do
	if v:IsA("BasePart") == false then
		error("Only parts are allowed")
	end
end
local basePosition = parts[1].Position

if detectOtherSize ~= false then
	local otherParts = {}
	for _, v in pairs(parts) do
		if math.abs(v.Position[detectOtherSize] - basePosition[detectOtherSize]) <= otherDifference then
			table.insert(otherParts, v)
		end
	end

	table.sort(otherParts, function(a, b)
		return if reverse == false
			then a.Position[direction] < b.Position[direction]
			else a.Position[direction] > b.Position[direction]
	end)

	for i, v in pairs(otherParts) do
		v.Name = otherSidePrefix .. i + existingCount
		table.remove(parts, table.find(parts, v))
	end
end

table.sort(parts, function(a, b)
	return if reverse == false
		then a.Position[direction] < b.Position[direction]
		else a.Position[direction] > b.Position[direction]
end)

for i, v in pairs(parts) do
	v.Name = prefix .. i + existingCount
end


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

local types = require(root.Plugin.types)
local color_picker = require(root.Plugin.vendor.color_picker)
local picker

type props = {
	id: string,
	initialColor: Color3,
}

--[[
	Color Picker
	Creates a color_picker hydrated component
]]
return function(scope: types.Scope, props: props): Color3?
	if picker == nil then
		picker = color_picker.new(props.id, plugin)
		table.insert(scope, picker)
		return
	end

	return picker:Prompt(props.initialColor)
end


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

local Fusion = require(root.Packages.fusion)
local types = require(root.Plugin.types)

type props = {
	pluginGuiId: string,
	InitialDockState: Enum.InitialDockState?,
	InitialEnabled: boolean?,
	InitialEnabledShouldOverrideRestore: boolean?,
	FloatingXSize: number?,
	FloatingYSize: number?,
	MinWidth: number?,
	MinHeight: number?,

	Title: Fusion.UsedAs<string>,
	Enabled: Fusion.UsedAs<boolean>,
	[Fusion.SpecialKey]: unknown,
}

--[[
	Widget
	Creates a DockWidgetPluginGui
]]
return function(scope: types.Scope, props: props)
	local pluginGuiId = scope.EatValue(props, "pluginGuiId") :: string
	local InitialDockState = scope.EatValue(props, "InitialDockState") :: Enum.InitialDockState?
	local InitialEnabled = scope.EatValue(props, "InitialEnabled") :: boolean?
	local InitialEnabledShouldOverrideRestore = scope.EatValue(props, "InitialEnabledShouldOverrideRestore") :: boolean?
	local FloatingXSize = scope.EatValue(props, "FloatingXSize") :: number?
	local FloatingYSize = scope.EatValue(props, "FloatingYSize") :: number?
	local MinWidth = scope.EatValue(props, "MinWidth") :: number?
	local MinHeight = scope.EatValue(props, "MinHeight") :: number?
	local widget = plugin:CreateDockWidgetPluginGui(
		pluginGuiId,
		DockWidgetPluginGuiInfo.new(
			InitialDockState or Enum.InitialDockState.Right,
			InitialEnabled or false,
			InitialEnabledShouldOverrideRestore or false,
			FloatingXSize or 0,
			FloatingYSize or 0,
			MinWidth or 0,
			MinHeight or 0
		)
	)
	widget.Name = pluginGuiId
	widget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling

	return scope:Hydrate(widget)(scope.CombineProperties({
		Title = props.Title,
		Enabled = props.Enabled,

		[Fusion.Children] = props[Fusion.Children],
	}, props))
end


--#selene: allow(shadowing)
-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent.Parent.Parent.Parent.Parent

local Fusion = require(root.Packages.fusion)
local RedonUI = require(root.Packages["redon-ui"])
local types = require(root.Plugin.types)
local popup = require(root.Plugin.Components.popup)
local chassisHandler = require(root.Plugin.chassisHandler)
local assets = require(root.Plugin.assets)
local log = require(root.Plugin.log)
local settings = require(root.Plugin.settings)
local utils = require(root.Plugin.utils)

local tween = require(script.Parent.tween)
local components = {
	tween = tween,
}

--[[
	faders.fader
	Creates the fader section in the faders screen
]]
return function(
	baseScope: types.Scope,
	props: {
		key: number,
		fader: types.faderSectionState,
		size: Fusion.Value<Vector2>,
		temporarlyPause: () -> () -> (),
		faders: Fusion.Value<{ types.faderSectionState }>,
		updateCurrentConfiguration: () -> (),
		holdingShift: Fusion.Value<boolean>,
		playing: Fusion.Value<boolean>,
		colors: Fusion.Value<{ chassisHandler.color }>,
		addFader: (atPoint: number) -> (),
		firstScope: types.Scope,
	}
): Fusion.Child
	local scope = baseScope:innerScope(components)
	local currentTheme = RedonUI.theme.theme:now()
	local dataSize = scope:Value(Vector2.one)

	return scope:base {
		Name = props.key,
		Size = UDim2.fromScale(0.1764705882, 0.9735449735),
		BackgroundColor3 = currentTheme.colors.mantle,
		CornerRadius = scope:Computed(function(use)
			return UDim.new(0, 0.0171428571 * use(props.size).Y) -- Once again, I shouldn't need to do this
		end),
		LayoutOrder = props.key,

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

			scope:base {
				Name = "Header",
				Size = UDim2.fromScale(1, 0.0574712644),
				BackgroundTransparency = 1,
				ZIndex = 2,

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

					scope:iconButton {
						Size = UDim2.fromScale(0.1785714286, 1),
						LayoutOrder = 1,
						CornerRadius = UDim.new(0.2, 0),
						Border = 2,
						Reactive = true,
						Image = assets.images.redonUI.remove,
						BackgroundColor3 = currentTheme.colors.crust,
						ImageColor3 = currentTheme.colors.text,

						[Fusion.OnEvent "Activated"] = function()
							local function remove()
								local unpause = props.temporarlyPause()
								local currentFaders = scope.peek(props.faders)
								table.remove(currentFaders, props.key)
								props.faders:set(currentFaders)
								props.updateCurrentConfiguration()
								unpause()
							end

							if props.holdingShift then
								remove()
								return
							end

							popup:addPopup(scope, {
								title = "Remove Fader",
								description = `Are you sure you want to delete fader <b>{props.key}</b>?\n<b>All data will be lost.</b>`,
								actions = {
									{
										text = "Cancel",
										type = "primary",
										key = 1,
										callback = function(newPopup)
											popup:removePopup(newPopup)
										end,
										keybind = Enum.KeyCode.Escape,
									},
									{
										text = "Delete",
										type = "danger",
										key = 2,
										callback = function(newPopup)
											popup:removePopup(newPopup)
											remove()
										end,
										keybind = Enum.KeyCode.Return,
									},
								},
							})
						end,
					},

					scope:base {
						Size = UDim2.fromScale(0.5357142857, 1),
						LayoutOrder = 2,
						CornerRadius = UDim.new(0.2, 0),
						Border = 1,
						BackgroundColor3 = currentTheme.colors.crust,
						BackgroundTransparency = scope:Computed(function(use)
							return if use(props.playing) == true then 1 else 0
						end),
						ZIndex = 2,
						[Fusion.Children] = Fusion.Child {
							scope:textBox {
								Size = UDim2.fromScale(1, 1),
								BackgroundTransparency = 1,
								Text = props.fader.lightName,
								[Fusion.Out "Text"] = props.fader.lightName :: unknown,
								Visible = scope:Computed(function(use)
									return use(props.playing) == false
								end),

								[Fusion.OnEvent "FocusLost"] = function()
									props.updateCurrentConfiguration()
								end,
							},

							scope:Computed(function(use, scope: typeof(scope))
								local knownColor = scope:Value(Color3.new(1, 1, 1))
								local color = scope:Computed(function(use)
									if use(props.playing) == false then
										return Color3.new(1, 1, 1)
									end

									local currentTweens = use(props.fader.data)
									local currentTween = currentTweens[use(props.fader.currentTween)]
									if currentTween == nil then
										log.warn(
											"Somehow the current tween is nil",
											currentTweens,
											scope.peek(props.fader.currentTween)
										)
										return Color3.new(1, 1, 1)
									end
									local colorId = use(currentTween.color)
									if colorId == nil then
										return scope.peek(knownColor)
									end
									local colorData = scope.peek(props.colors)[colorId]
									local finalColor = if colorData == nil
										then Color3.new(1, 1, 1)
										else if utils.bloomColors[colorData.name] ~= nil
											then utils.bloomColors[colorData.name]
											else colorData.lightoColor
									knownColor:set(finalColor)
									return finalColor
								end)
								local visible = scope:Computed(function(use)
									return use(props.playing) == true
								end)

								if use(settings.values.fakeBloom) then
									return {
										scope:base {
											Size = UDim2.fromScale(1, 1),
											BackgroundColor3 = Color3.new(1, 1, 1),
											BackgroundTransparency = props.fader.currentValue,
											CornerRadius = UDim.new(0.2, 0),
											ZIndex = 2,
											Visible = visible,
										},

										scope:base {
											ClassName = "ImageLabel",
											Name = "Light",
											AnchorPoint = Vector2.new(0.5, 0.5),
											BackgroundTransparency = 1,
											Image = assets.images.elsCreator.bloom,
											ImageColor3 = color,
											ImageTransparency = props.fader.currentValue,
											Position = UDim2.fromScale(0.5, 0.5),
											ScaleType = Enum.ScaleType.Slice,
											Size = UDim2.new(1, 25, 1, 25),
											SliceCenter = Rect.new(256, 256, 256, 256),
											Visible = visible,
										},

										scope:base {
											ClassName = "ImageLabel",
											Name = "Light1",
											AnchorPoint = Vector2.new(0.5, 0.5),
											BackgroundTransparency = 1,
											Image = assets.images.elsCreator.bloom,
											ImageColor3 = color,
											ImageTransparency = scope:Computed(function(use)
												return -0.2 * (1 - use(props.fader.currentValue)) + 1
											end),
											Position = UDim2.fromScale(0.5, 0.5),
											ScaleType = Enum.ScaleType.Slice,
											Size = UDim2.new(1, 150, 1, 150),
											SliceCenter = Rect.new(256, 256, 256, 256),
											Visible = visible,
										},

										scope:base {
											ClassName = "ImageLabel",
											Name = "Light2",
											AnchorPoint = Vector2.new(0.5, 0.5),
											BackgroundTransparency = 1,
											Image = assets.images.elsCreator.bloom,
											ImageColor3 = color,
											ImageTransparency = scope:Computed(function(use)
												return -0.5 * (1 - use(props.fader.currentValue)) + 1
											end),
											Position = UDim2.fromScale(0.5, 0.5),
											ScaleType = Enum.ScaleType.Slice,
											Size = UDim2.new(1, 200, 1, 200),
											SliceCenter = Rect.new(256, 256, 256, 256),
											Visible = visible,
										},
									}
								end

								return {
									scope:base {
										Size = UDim2.fromScale(1, 1),
										BackgroundColor3 = color,
										BackgroundTransparency = props.fader.currentValue,
										CornerRadius = UDim.new(0.2, 0),
										ZIndex = 2,
										Visible = visible,
									},
								}
							end),
						},
					},

					scope:iconButton {
						Size = UDim2.fromScale(0.1785714286, 1),
						LayoutOrder = 3,
						CornerRadius = UDim.new(0.2, 0),
						Border = 2,
						Reactive = true,
						Image = assets.images.redonUI.add,
						BackgroundColor3 = currentTheme.colors.crust,
						ImageColor3 = currentTheme.colors.text,

						[Fusion.OnEvent "Activated"] = function()
							props.addFader(props.key + 1)
						end,
					},
				},
			},

			scope:base {
				ClassName = "ScrollingFrame",
				Name = "Data",
				Size = UDim2.fromScale(1, 0.9137931034), -- 150, 795
				Position = UDim2.fromScale(0.5, 1),
				AnchorPoint = Vector2.new(0.5, 1),
				BackgroundTransparency = 1,
				CanvasSize = UDim2.new(),
				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,
				[Fusion.Out "AbsoluteSize"] = dataSize :: unknown,

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

					scope:New("UIPadding") {
						PaddingTop = UDim.new(0, 1),
						PaddingBottom = UDim.new(0, 1),
					},

					scope:ForPairs(props.fader.data, function(_, scope: typeof(scope), key: number, tween)
						return key,
							scope:tween {
								key = key,
								tween = tween,
								dataSize = dataSize,
								firstScope = props.firstScope,
								updateCurrentConfiguration = props.updateCurrentConfiguration,
								colors = props.colors,
								fader = props.fader,
								playing = props.playing,
							}
					end),

					scope:base {
						Size = UDim2.fromScale(0.5357142857, 0.0628930818),
						BackgroundTransparency = 1,
						LayoutOrder = 4000,

						[Fusion.Children] = {
							scope:iconButton {
								Size = UDim2.fromScale(0.3333333333, 1),
								LayoutOrder = 1,
								CornerRadius = UDim.new(0.2, 0),
								Border = 1,
								Reactive = true,
								Image = assets.images.redonUI.remove,
								BackgroundColor3 = currentTheme.colors.crust,
								ImageColor3 = currentTheme.colors.text,

								[Fusion.OnEvent "Activated"] = function()
									if #scope.peek(props.fader.data) == 1 then
										log.warn("Cannot remove the last tween")
										return
									end
									local deleting = #scope.peek(props.fader.data)

									local function remove()
										local currentData = scope.peek(props.fader.data)
										table.remove(currentData, deleting)
										props.fader.data:set(currentData)
									end

									if props.holdingShift then
										remove()
										return
									end

									popup:addPopup(scope, {
										title = "Remove Tween",
										description = `Are you sure you want to delete tween <b>{deleting}</b>?\n<b>All data will be lost.</b>`,
										actions = {
											{
												text = "Cancel",
												type = "primary",
												key = 1,
												callback = function(newPopup)
													popup:removePopup(newPopup)
												end,
												keybind = Enum.KeyCode.Escape,
											},
											{
												text = "Delete",
												type = "danger",
												key = 2,
												callback = function(newPopup)
													popup:removePopup(newPopup)
													remove()
												end,
												keybind = Enum.KeyCode.Return,
											},
										},
									})
								end,
							},

							scope:iconButton {
								Size = UDim2.fromScale(0.3333333333, 1),
								Position = UDim2.fromScale(1, 0),
								AnchorPoint = Vector2.new(1, 0),
								LayoutOrder = 2,
								CornerRadius = UDim.new(0.2, 0),
								Border = 1,
								Reactive = true,
								Image = assets.images.redonUI.add,
								BackgroundColor3 = currentTheme.colors.crust,
								ImageColor3 = currentTheme.colors.text,

								[Fusion.OnEvent "Activated"] = function()
									local currentData = scope.peek(props.fader.data)
									table.insert(
										currentData,
										{
											type = props.firstScope:Value("Wait"),
											time = props.firstScope:Value(0.5),
										} :: any
									)
									props.fader.data:set(currentData)
								end,
							},
						},
					},
				},
			},
		},
	}
end


--#selene: allow(shadowing)
-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent.Parent.Parent.Parent

local Fusion = require(root.Packages.fusion)
local RedonUI = require(root.Packages["redon-ui"])
local types = require(root.Plugin.types)
local popup = require(root.Plugin.Components.popup)
local chassisHandler = require(root.Plugin.chassisHandler)
local assets = require(root.Plugin.assets)
local log = require(root.Plugin.log)
local utils = require(root.Plugin.utils)
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")

local faderComponent = require(script.fader)
local components = {
	faderComponent = faderComponent,
}

type props = {
	UnsavedChanges: Fusion.Value<boolean>,
	Enabled: Fusion.UsedAs<boolean>,
	Mode: Fusion.Value<types.mode>,
	CurrentPanel: Fusion.Value<types.mainPanels>,
	CurrentPage: Fusion.Value<types.pages?>,
	CurrentVehicle: Fusion.Value<Model | string?>,
	CurrentPattern: Fusion.Value<types.selectedPattern?>,
}

--[[
	Faders
	Creates the faders screen for the main app
]]
return function(baseScope: types.Scope, props: props): Fusion.Child
	local scope = baseScope:innerScope(components)
	local currentTheme = RedonUI.theme.theme:now()
	local firstScope = scope:innerScope()

	local currentConfiguration: chassisHandler.chassisConfiguration? = nil
	local colors: Fusion.Value<{ chassisHandler.color }> = scope:Value(chassisHandler.defaults.colors)
	local playing = scope:Value(false)
	local function temporarlyPause()
		local wasPlaying = scope.peek(playing)
		playing:set(false)
		return function()
			playing:set(wasPlaying)
		end
	end
	local holdingShift = scope:Value(false) -- This is used to skip certain popups
	local faders: Fusion.Value<{ types.faderSectionState }> = scope:Value({})

	local function updateCurrentConfiguration()
		local currentPattern = scope.peek(props.CurrentPattern)
		if currentPattern ~= nil then
			local assembledFaders: { types.faderSection } = {}
			for faderId, fader in scope.peek(faders) do
				local assembledTweens: { types.faderData } = {}
				for tweenId, tween in scope.peek(fader.data) do
					table.insert(assembledTweens, tweenId, {
						type = scope.peek(tween.type) :: any,
						time = scope.peek(tween.time),
						goal = if tween.goal then scope.peek(tween.goal) else nil,
						color = if tween.color then scope.peek(tween.color) else nil,
						easingStyle = if tween.easingStyle then scope.peek(tween.easingStyle) else nil,
						easingDirection = if tween.easingDirection then scope.peek(tween.easingDirection) else nil,
					})
				end
				table.insert(assembledFaders, faderId, {
					lightName = scope.peek(fader.lightName),
					data = assembledTweens,
				})
			end

			if utils.compareTables(currentPattern.pattern.faders, assembledFaders) == true then
				return
			end

			currentPattern.pattern.faders = assembledFaders
			props.UnsavedChanges:set(true)
		end
	end

	local function promptReset()
		popup:addPopup(scope, {
			title = "Reset Faders",
			description = "Are you sure you want to reset all faders?\n<b>All data will be lost.</b>",
			actions = {
				{
					text = "Cancel",
					type = "primary",
					key = 1,
					callback = function(newPopup)
						popup:removePopup(newPopup)
					end,
					keybind = Enum.KeyCode.Escape,
				},
				{
					text = "Reset",
					type = "danger",
					key = 2,
					callback = function(newPopup)
						popup:removePopup(newPopup)
						playing:set(false)
						faders:set({})
						updateCurrentConfiguration()
					end,
				},
			},
		})
	end

	scope:Observer(props.CurrentPattern):onChange(function()
		local currentPattern = scope.peek(props.CurrentPattern)
		local currentVehicle = scope.peek(props.CurrentVehicle)
		if currentPattern ~= nil then
			playing:set(false)
			if typeof(currentVehicle) == "Instance" then
				currentConfiguration = chassisHandler:getConfiguration(currentVehicle)
				if currentConfiguration ~= nil then
					colors:set(currentConfiguration.lightSettings.colors)
				else
					colors:set(chassisHandler.defaults.colors)
				end
			end

			local newFaders = {}
			for faderId, fader in currentPattern.pattern.faders do
				local newFader = {
					lightName = firstScope:Value(fader.lightName),
					currentPercentage = firstScope:Value(0),
					currentTween = firstScope:Value(1),
					currentValue = firstScope:Value(0),
					previousGoal = 1,
					data = firstScope:Value({}),
				}
				local newData = {}
				for tweenId, tween in fader.data do
					if tween.type == "Simple" then
						table.insert(newData, tweenId, {
							type = firstScope:Value(tween.type),
							time = firstScope:Value(tween.time),
							goal = tween.goal and firstScope:Value(tween.goal) or nil,
							color = tween.color and scope:Value(tween.color) or nil,
						})
					elseif tween.type == "Advanced" then
						table.insert(newData, tweenId, {
							type = firstScope:Value(tween.type),
							time = firstScope:Value(tween.time),
							goal = tween.goal and firstScope:Value(tween.goal) or nil,
							color = tween.color and firstScope:Value(tween.color) or nil,
							easingStyle = tween.easingStyle and firstScope:Value(tween.easingStyle) or nil,
							easingDirection = tween.easingDirection and firstScope:Value(tween.easingDirection) or nil,
						})
					elseif tween.type == "Wait" then
						table.insert(newData, tweenId, {
							type = firstScope:Value(tween.type),
							time = firstScope:Value(tween.time),
						})
					end
				end
				newFader.data:set(newData)
				table.insert(newFaders, faderId, newFader)
			end
			log.debug(faders)
			faders:set(newFaders)
		end
	end)

	local function addFader(atPoint: number?)
		local unpause = temporarlyPause()
		local currentFaders = scope.peek(faders)
		if atPoint == nil then
			atPoint = #currentFaders + 1
		end
		table.insert(currentFaders, atPoint :: number, {
			lightName = firstScope:Value("Fader " .. atPoint :: number),
			data = firstScope:Value({
				{
					type = firstScope:Value("Simple" :: any), -- me when luau types
					time = firstScope:Value(0.5),
					goal = firstScope:Value(0),
					color = firstScope:Value(4),
				},
				{
					type = firstScope:Value("Simple" :: any), -- me when luau types
					time = firstScope:Value(0.5),
					goal = firstScope:Value(1),
					color = firstScope:Value(4),
				},
			}),
			currentTween = firstScope:Value(1),
			currentPercentage = firstScope:Value(0),
			currentValue = firstScope:Value(0),
		})

		faders:set(currentFaders)
		updateCurrentConfiguration()
		unpause()
	end

	-- player
	local connection: RBXScriptConnection? = nil
	scope:Observer(playing):onChange(function()
		local isPlaying = scope.peek(playing)
		local currentFaders = scope.peek(faders)
		if isPlaying == true then
			log.debug("Playing")
			for _, data in currentFaders do
				if data.previousGoal == nil then
					data.previousGoal = 1
					data.currentValue:set(1)
				end
			end
			connection = RunService.Heartbeat:Connect(function(deltaTime: number)
				for _, data in currentFaders do
					local currentTweens = scope.peek(data.data)
					local tweenId = scope.peek(data.currentTween)
					local currentPercentage = scope.peek(data.currentPercentage)
					if currentPercentage >= 1 then
						if currentTweens[tweenId] ~= nil and scope.peek(currentTweens[tweenId].type) ~= "Wait" then
							data.previousGoal = scope.peek(currentTweens[tweenId].goal)
						end
						tweenId += 1
						currentPercentage = 0
					end
					if tweenId > #currentTweens then
						if currentTweens[tweenId] ~= nil and scope.peek(currentTweens[tweenId].type) ~= "Wait" then
							data.previousGoal = scope.peek(currentTweens[tweenId].goal)
						end
						tweenId = 1
						currentPercentage = 0
					end
					data.currentTween:set(tweenId) -- Fusion wont push this change if its the same value
					local currentTween = currentTweens[tweenId]
					currentPercentage += deltaTime / scope.peek(currentTween.time)
					data.currentPercentage:set(currentPercentage)
					if scope.peek(currentTween.type) ~= "Wait" then
						local previousGoal = data.previousGoal or 1
						local goal: number = scope.peek(currentTweens[tweenId].goal) or 0
						-- log.debug(tweenId, scope.peek(currentTween.time), goal, previousGoal)

						data.currentValue:set(
							(goal - previousGoal)
									* TweenService:GetValue(
										currentPercentage,
										scope.peek(currentTween.easingStyle) or Enum.EasingStyle.Linear,
										scope.peek(currentTween.easingDirection) or Enum.EasingDirection.InOut
									)
								+ previousGoal
						)
						-- else
						-- log.debug(tweenId, scope.peek(currentTween.time))
					end
				end
			end)
		else
			log.debug("Stopping")
			if connection ~= nil then
				connection:Disconnect()
				connection = nil
			end

			-- Reset the faders to ensure they stay in sync
			for _, data in currentFaders do
				data.currentTween:set(1)
				data.currentPercentage:set(0)
				data.currentValue:set(1)
				data.previousGoal = 1
			end
		end
	end)

	scope:Observer(props.Enabled):onChange(function()
		local enabled = scope.peek(props.Enabled)
		if enabled == false then
			playing:set(false)
		end
	end)

	scope:Observer(props.CurrentPage):onBind(function()
		local currentPage = scope.peek(props.CurrentPage)
		if currentPage ~= "Faders" then
			playing:set(false)
		end
	end)

	local playHovered = scope:Value(false)
	local size = scope:Value(Vector2.one)
	local sectionsTotalSpace = scope:Value(Vector2.one)
	return scope:base {
		Name = "Faders",
		AnchorPoint = Vector2.new(0.5, 0),
		Position = scope:Spring(
			scope:Computed(function(use)
				local currentPage = use(props.CurrentPage)
				if currentPage == "Faders" then
					return UDim2.fromScale(0.5, 0.0462962963)
				elseif currentPage == "Rotators" then
					return UDim2.fromScale(-1, 0.0462962963)
				end
				return UDim2.fromScale(1.5, 0.0462962963)
			end),
			20,
			0.75
		),
		Size = UDim2.fromScale(1, 0.9537037037),
		BackgroundTransparency = 1,

		[Fusion.OnEvent "InputBegan"] = function(input: InputObject)
			if input.UserInputType == Enum.UserInputType.Keyboard then
				if input.KeyCode == Enum.KeyCode.Space or input.KeyCode == Enum.KeyCode.P then
					playing:set(not scope.peek(playing))
				elseif input.KeyCode == Enum.KeyCode.LeftShift then
					holdingShift:set(true)
				end
			end
		end :: unknown,

		[Fusion.OnEvent "InputEnded"] = function(input: InputObject)
			if input.UserInputType == Enum.UserInputType.Keyboard then
				if input.KeyCode == Enum.KeyCode.LeftShift then
					holdingShift:set(false)
				end
			end
		end :: unknown,

		[Fusion.Children] = {
			scope:base {
				Name = "Header",
				AnchorPoint = Vector2.new(0, 0),
				Position = UDim2.fromScale(0, 0),
				Size = UDim2.fromScale(1, 0.0582524272),
				BackgroundColor3 = currentTheme.colors.mantle,

				[Fusion.Children] = {
					scope:base {
						ClassName = "ScrollingFrame",
						AnchorPoint = Vector2.new(0.5, 0.5),
						Position = UDim2.fromScale(0.5, 0.5),
						Size = UDim2.fromScale(1, 1),
						BackgroundTransparency = 1,
						CanvasSize = UDim2.new(),
						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.X,
						ClipsDescendants = false,

						[Fusion.Children] = Fusion.Child {
							scope:New("UIAspectRatioConstraint") {
								AspectRatio = 32,
							},

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

							scope:iconButton {
								Size = UDim2.fromScale(0.0260416667, 0.8333333333),
								CornerRadius = UDim.new(0.2, 0),
								Border = 2,
								Reactive = true,
								LayoutOrder = 100,
								Image = scope:Computed(function(use)
									local isPlaying = use(playing)
									return if isPlaying
										then assets.images.redonUI.pause
										else if use(playHovered)
											then assets.images.redonUI.playFilled
											else assets.images.redonUI.play
								end),
								BackgroundColor3 = currentTheme.colors.mantle,
								ImageColor3 = currentTheme.colors.text,
								MouseEnter = function()
									playHovered:set(true)
								end,
								MouseLeave = function()
									playHovered:set(false)
								end,
								[Fusion.OnEvent "Activated"] = function()
									playing:set(not scope.peek(playing))
								end,
							},

							scope:iconButton {
								Size = UDim2.fromScale(0.0260416667, 0.8333333333),
								CornerRadius = UDim.new(0.2, 0),
								Border = 2,
								Reactive = true,
								LayoutOrder = 102,
								Image = assets.images.redonUI.refresh,
								BackgroundColor3 = currentTheme.colors.mantle,
								ImageColor3 = currentTheme.colors.text,
								[Fusion.OnEvent "Activated"] = function()
									promptReset()
								end,
							},
						},
					},
				},
			},

			scope:base {
				Size = UDim2.fromScale(1, 1),
				AnchorPoint = Vector2.new(0.5, 1),
				Position = UDim2.fromScale(0.5, 1),
				BackgroundTransparency = 1,
				ClipsDescendants = true,

				[Fusion.Children] = {
					scope:New("UIAspectRatioConstraint") {
						AspectRatio = 1.9793814433,
					},

					scope:base {
						ClassName = "ScrollingFrame",
						Name = "Content",
						Size = UDim2.fromScale(0.9739583333, 0.9174757282), -- 1870, 945
						AnchorPoint = Vector2.new(0.5, 1),
						Position = UDim2.fromScale(0.5, 1),
						BackgroundTransparency = 1,
						CanvasSize = UDim2.new(),
						AutomaticCanvasSize = Enum.AutomaticSize.X,
						ScrollBarThickness = 3,
						ScrollBarImageColor3 = currentTheme.colors.text,
						TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						[Fusion.Out "AbsoluteSize"] = size :: unknown,
						ClipsDescendants = false,

						[Fusion.Children] = Fusion.Child {
							scope:New("UIListLayout") {
								Padding = UDim.new(0.0130208333, 0),
								FillDirection = Enum.FillDirection.Horizontal,
								HorizontalAlignment = scope:Computed(function(use)
									return if use(sectionsTotalSpace).X > use(size).X
										then Enum.HorizontalAlignment.Left
										else Enum.HorizontalAlignment.Center
								end),
								VerticalAlignment = Enum.VerticalAlignment.Top,
								SortOrder = Enum.SortOrder.LayoutOrder,
								[Fusion.Out "AbsoluteContentSize"] = sectionsTotalSpace :: unknown,
							},

							scope:ForPairs(faders, function(_, scope: typeof(scope), key: number, fader)
								return key,
									scope:faderComponent {
										key = key,
										fader = fader,
										size = size,
										temporarlyPause = temporarlyPause,
										faders = faders,
										updateCurrentConfiguration = updateCurrentConfiguration,
										holdingShift = holdingShift,
										playing = playing,
										colors = colors,
										addFader = addFader,
										firstScope = firstScope,
									}
							end),
						},
					},

					scope:textButton {
						Size = UDim2.fromScale(0.1822916667, 0.0776699029),
						AnchorPoint = Vector2.new(0.5, 0.5),
						Position = UDim2.fromScale(0.5, 0.5),
						BackgroundColor3 = Color3.new(1, 1, 1),
						Gradient = ColorSequence.new(currentTheme.colors.sky, currentTheme.colors.sapphire),
						CornerRadius = UDim.new(0.2, 0),
						Text = "Add Section",
						TextScaled = true,
						MaxTextSize = 40,
						TextColor3 = currentTheme.colors.mantle,
						Border = 2,
						Reactive = true,
						ButtonGlow = true,
						ButtonGlowColor3 = if currentTheme.colors.white == Color3.new(1, 1, 1)
							then currentTheme.colors.text
							else currentTheme.colors.base,
						Shadow = true,
						ShadowColor3 = currentTheme.colors.sky,
						Visible = scope:Computed(function(use)
							return #use(faders) == 0
						end),

						[Fusion.OnEvent "Activated"] = function()
							addFader()
						end,
					},
				},
			},
		},
	}
end


--#selene: allow(shadowing)
-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent.Parent.Parent.Parent.Parent

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

--[[
	faders.tween
	Creates the controls for the fader
]]
return function(
	scope: types.Scope,
	props: {
		key: number,
		tween: types.faderTweenDataState,
		dataSize: Fusion.Value<Vector2>,
		firstScope: types.Scope,
		updateCurrentConfiguration: () -> (),
		colors: Fusion.Value<{ chassisHandler.color }>,
		fader: types.faderSectionState,
		playing: Fusion.Value<boolean>,
	}
): Fusion.Child
	local currentTheme = RedonUI.theme.theme:now()

	local controlSize = scope:Computed(function(use)
		return UDim2.new(1, 0, 0, 0.0628930818 * use(props.dataSize).Y)
	end)
	local cornerRadius = scope:Computed(function(use)
		return UDim.new(0, 0.0125786164 * use(props.dataSize).Y)
	end)
	local baseDropdown = scope:dropdown {
		Size = controlSize,
		BackgroundColor3 = currentTheme.colors.crust,
		CornerRadius = cornerRadius,
		Border = 1,
		Text = props.tween.type :: any,
		TextXAlignment = Enum.TextXAlignment.Left,
		LayoutOrder = 1,
		DropdownHeight = 150,
		Options = {
			"Simple",
			"Advanced",
			"Wait",
		},
		OnOptionSelected = function(_, newType)
			if newType == "Wait" then
				props.tween.goal = nil
				props.tween.color = nil
				props.tween.easingStyle = nil
				props.tween.easingDirection = nil
			elseif newType == "Simple" then
				props.tween.goal = if props.tween.goal ~= nil then props.tween.goal else props.firstScope:Value(0)
				props.tween.color = if props.tween.color ~= nil then props.tween.color else props.firstScope:Value(4)
				props.tween.easingStyle = nil
				props.tween.easingDirection = nil
			elseif newType == "Advanced" then
				props.tween.goal = if props.tween.goal ~= nil then props.tween.goal else props.firstScope:Value(0)
				props.tween.color = if props.tween.color ~= nil then props.tween.color else props.firstScope:Value(4)
				props.tween.easingStyle = if props.tween.easingStyle ~= nil
					then props.tween.easingStyle
					else props.firstScope:Value(Enum.EasingStyle.Linear)
				props.tween.easingDirection = if props.tween.easingDirection ~= nil
					then props.tween.easingDirection
					else props.firstScope:Value(Enum.EasingDirection.InOut)
			end
			props.tween.type:set(newType :: "Simple" | "Advanced" | "Wait")
			props.updateCurrentConfiguration()
		end,
	}
	local baseDropdownFrame = baseDropdown[2] :: Frame
	baseDropdownFrame.LayoutOrder = 2
	local currentTime = scope:Value(tostring(scope.peek(props.tween.time)))
	local baseControls: { Fusion.Child } = {
		baseDropdown,
		scope:textBox {
			Size = controlSize,
			BackgroundColor3 = currentTheme.colors.crust,
			CornerRadius = UDim.new(0.2, 0),
			Border = 1,
			Text = currentTime,
			[Fusion.Out "Text"] = currentTime :: unknown,
			PlaceholderText = scope:Computed(function(use)
				return if use(props.tween.type) == "Wait" then "Wait Time" else "Time"
			end),
			PlaceholderColor3 = currentTheme.colors.subtext0,
			LayoutOrder = 7,
			[Fusion.OnEvent "FocusLost"] = function()
				props.tween.time:set(tonumber(scope.peek(currentTime)) or 0)
				currentTime:set(tostring(scope.peek(currentTime)))
				props.updateCurrentConfiguration()
			end,
		},
	}

	local controls = scope:Computed(function(use, scope: typeof(scope))
		local controls: { Fusion.Child } = baseControls
		local currentType = use(props.tween.type)

		if currentType == "Simple" or currentType == "Advanced" then
			if props.tween.color ~= nil then
				local colorDropdown = scope:dropdown {
					Size = controlSize,
					BackgroundColor3 = currentTheme.colors.crust,
					CornerRadius = cornerRadius,
					Border = 1,
					Text = scope:Computed(function(use)
						local colorId = use(props.tween.color)
						return if colorId == 0 then "None" else use(props.colors)[colorId].name
					end),
					TextXAlignment = Enum.TextXAlignment.Left,
					LayoutOrder = 3,
					DropdownHeight = 150,
					Options = scope:Computed(function(use)
						local newColors: { string } = {}
						for colorId, color in pairs(use(props.colors)) do
							table.insert(newColors, colorId, color.name)
						end
						table.insert(newColors, 1, "None")
						return newColors
					end),
					OptionOverrides = scope:Computed(function(use)
						local textColors = {}
						for colorId, color in pairs(use(props.colors)) do
							textColors[colorId] = {
								TextColor3 = utils.getColorByName(color.name, currentTheme.colors.text),
							}
						end
						table.insert(textColors, 1, {
							TextColor3 = currentTheme.colors.text,
						})
						return textColors :: any
					end),
					TextColor3 = scope:Computed(function(use)
						local colorId = use(props.tween.color)
						return if colorId == 0
							then currentTheme.colors.text
							else utils.getColorByName(use(props.colors)[colorId].name, currentTheme.colors.text)
					end),
					OnOptionSelected = function(colorId: number)
						props.tween.color:set(colorId - 1)
						props.updateCurrentConfiguration()
					end,
				}
				local colorDropdownFrame = colorDropdown[2] :: Frame
				colorDropdownFrame.LayoutOrder = 4
				table.insert(controls, colorDropdown)
			end
			if props.tween.goal ~= nil then
				local currentText = scope:Value(tostring(scope.peek(props.tween.goal)))
				table.insert(
					controls,
					scope:textBox {
						Size = controlSize,
						BackgroundColor3 = currentTheme.colors.crust,
						CornerRadius = UDim.new(0.2, 0),
						Border = 1,
						Text = currentText,
						[Fusion.Out "Text"] = currentText :: unknown,
						PlaceholderText = "Goal",
						PlaceholderColor3 = currentTheme.colors.subtext0,
						LayoutOrder = 6,
						[Fusion.OnEvent "FocusLost"] = function()
							props.tween.goal:set(tonumber(scope.peek(currentText)) or 0)
							currentText:set(tostring(scope.peek(props.tween.goal)))
							props.updateCurrentConfiguration()
						end,
					}
				)
			end
			if currentType == "Advanced" then
				if props.tween.easingStyle ~= nil then
					local options = {}
					for _, easingStyle: Enum.EasingStyle in pairs(Enum.EasingStyle:GetEnumItems()) do
						table.insert(options, easingStyle.Name)
					end
					local easingStyleDropdown = scope:dropdown {
						Size = controlSize,
						BackgroundColor3 = currentTheme.colors.crust,
						CornerRadius = cornerRadius,
						Border = 1,
						Text = scope:Computed(function(use)
							return use(props.tween.easingStyle).Name
						end),
						TextXAlignment = Enum.TextXAlignment.Left,
						LayoutOrder = 8,
						DropdownHeight = 150,
						Options = options,
						OnOptionSelected = function(_, newValue)
							local success, newEasingStyle = pcall(function()
								return (Enum.EasingStyle :: any)[newValue]
							end)
							if not success then
								return
							end
							props.tween.easingStyle:set(newEasingStyle :: Enum.EasingStyle)
							props.updateCurrentConfiguration()
						end,
					}
					local easingStyleDropdownFrame = easingStyleDropdown[2] :: Frame
					easingStyleDropdownFrame.LayoutOrder = 9
					table.insert(controls, easingStyleDropdown)
				end
				if props.tween.easingDirection ~= nil then
					local options = {}
					for _, easingStyle: Enum.EasingDirection in pairs(Enum.EasingDirection:GetEnumItems()) do
						table.insert(options, easingStyle.Name)
					end
					local easingStyleDropdown = scope:dropdown {
						Size = controlSize,
						BackgroundColor3 = currentTheme.colors.crust,
						CornerRadius = cornerRadius,
						Border = 1,
						Text = scope:Computed(function(use)
							return use(props.tween.easingDirection).Name
						end),
						TextXAlignment = Enum.TextXAlignment.Left,
						LayoutOrder = 10,
						DropdownHeight = 150,
						Options = options,
						OnOptionSelected = function(_, newValue)
							local success, newEasingDirection = pcall(function()
								return (Enum.EasingDirection :: any)[newValue]
							end)
							if not success then
								return
							end
							props.tween.easingDirection:set(newEasingDirection :: Enum.EasingDirection)
							props.updateCurrentConfiguration()
						end,
					}
					local easingStyleDropdownFrame = easingStyleDropdown[2] :: Frame
					easingStyleDropdownFrame.LayoutOrder = 11
					table.insert(controls, easingStyleDropdown)
				end
			end
		end

		return controls
	end)

	return scope:base {
		Name = props.key,
		Size = UDim2.fromScale(0.5357142857, 0),
		BackgroundTransparency = 1,
		LayoutOrder = props.key,
		AutomaticSize = Enum.AutomaticSize.Y,

		[Fusion.Children] = {
			scope:base {
				ClassName = "ImageLabel",
				Size = UDim2.fromScale(0.1333333333, 1),
				Position = UDim2.fromScale(-0.0666666667, 0),
				AnchorPoint = Vector2.new(1, 0),
				BackgroundTransparency = 1,
				Image = assets.images.elsCreator.box,
				ResampleMode = Enum.ResamplerMode.Pixelated,
				ScaleType = Enum.ScaleType.Slice,
				SliceCenter = Rect.new(2, 2, 18, 33),

				[Fusion.Children] = {
					scope:New("UIGradient") {
						Name = "UIGradient",
						Color = ColorSequence.new({
							ColorSequenceKeypoint.new(0, currentTheme.colors.blue),
							ColorSequenceKeypoint.new(0.001, currentTheme.colors.text),
							ColorSequenceKeypoint.new(1, currentTheme.colors.text),
						}),
						Offset = scope:Computed(function(use)
							local percent = 0
							if use(props.playing) == true then
								if use(props.fader.currentTween) == props.key then
									percent = use(props.fader.currentPercentage)
								elseif use(props.fader.currentTween) > props.key then
									percent = 1
								end
							end
							return Vector2.new(0, percent)
						end),
						Rotation = 90,
					},
				},
			},

			scope:base {
				Name = "Controls",
				Size = UDim2.fromScale(1, 0),
				Position = UDim2.fromScale(0, 0),
				BackgroundTransparency = 1,
				AutomaticSize = Enum.AutomaticSize.Y,

				[Fusion.Children] = Fusion.Child {
					scope:New("UIListLayout") {
						Padding = scope:Computed(function(use)
							return UDim.new(0, 0.0125786164 * use(props.dataSize).Y)
						end),
						FillDirection = Enum.FillDirection.Vertical,
						HorizontalAlignment = Enum.HorizontalAlignment.Center,
						VerticalAlignment = Enum.VerticalAlignment.Top,
						SortOrder = Enum.SortOrder.LayoutOrder,
					},

					controls,
				},
			},
		},
	}
end


--#selene: allow(shadowing)
-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent.Parent.Parent.Parent.Parent

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

--[=[
	flashers.sections
	Creates the sections for the flashers editor
]=]
return function(
	scope: types.Scope,
	props: {
		columnKey: number,
		columnData: any,
		sectionKey: number,
		currentlyHovering: Fusion.Value<number>,
		columnPositions: Fusion.Value<{ [number]: { scope: any, value: Fusion.Value<Vector2> } }>,
		drawing: Fusion.Value<number>,
		size: Fusion.Value<Vector2>,
		updateCurrentConfiguration: () -> (),
		locked: Fusion.Value<boolean>,
		currentColor: Fusion.Value<number>,
		colors: Fusion.Value<{ chassisHandler.color }>,
		lockedTo: Fusion.Value<{ section: number, column: number }?>,
	}
)
	local currentTheme = RedonUI.theme.theme:now()

	return scope:base {
		Name = props.columnKey,
		Size = scope:Computed(function(use)
			return UDim2.new(0, 0.0401069519 * use(props.size).X, 1, 0)
		end),
		BackgroundTransparency = 1,
		LayoutOrder = props.columnKey,

		[Fusion.OnEvent "MouseEnter"] = function()
			props.currentlyHovering:set(props.columnKey)
		end :: unknown,

		[Fusion.OnChange "AbsolutePosition"] = function(newPosition: Vector2)
			local currentColumnPositions = scope.peek(props.columnPositions)
			if currentColumnPositions[props.columnKey] == nil then
				return
			end
			currentColumnPositions[props.columnKey].value:set(newPosition)
		end,

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

			scope:ForPairs(props.columnData.rows, function(use, scope: typeof(scope), rowKey: number, rowColor: number)
				local function update()
					if scope.peek(props.drawing) == 0 then
						return
					end
					local isLocked = scope.peek(props.locked)
					log.debug(isLocked)
					if isLocked == true then
						local lockedTo = scope.peek(props.lockedTo)
						log.debug(props.lockedTo)
						if lockedTo ~= nil then
							if lockedTo.section ~= props.sectionKey or lockedTo.column ~= props.columnKey then
								return
							end
						else
							props.lockedTo:set({
								section = props.sectionKey,
								column = props.columnKey,
							})
						end
					end

					local currentRows = scope.peek(props.columnData.rows)
					if scope.peek(props.drawing) == 1 then
						currentRows[rowKey] = scope.peek(props.currentColor)
					elseif scope.peek(props.drawing) == -1 then
						currentRows[rowKey] = 0
					end
					props.columnData.rows:set(currentRows)
					props.updateCurrentConfiguration()
				end

				local color = use(props.colors)[rowColor]
				return rowKey,
					scope:base {
						Name = rowKey,
						Size = UDim2.fromScale(1, 0.0447761194),
						BackgroundColor3 = if rowColor == 0
							then currentTheme.colors.base
							else utils.getColorByName(color.name, color.lightoColor),
						CornerRadius = UDim.new(0.1666666667, 0),
						LayoutOrder = rowKey,

						[Fusion.OnEvent "MouseEnter"] = function()
							props.currentlyHovering:set(props.columnKey)
							update()
						end :: unknown,
						[Fusion.OnEvent "InputBegan"] = function(input: InputObject)
							if input.UserInputType == Enum.UserInputType.MouseButton1 then
								props.drawing:set(1)
							elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
								props.drawing:set(-1)
							end
							update()
						end,
					}
			end),

			scope:base {
				Size = UDim2.fromScale(1, 0.0447761194),
				BackgroundTransparency = 1,
				LayoutOrder = 4000,
			},
			scope:base {
				Size = UDim2.fromScale(1, 0.0447761194),
				BackgroundTransparency = 1,
				LayoutOrder = 4000,
			},
		},
	}
end


--#selene: allow(shadowing)
-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent.Parent.Parent.Parent.Parent

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

--[=[
	flashers.columnHeader
	Creates the header for a column
]=]
return function(
	scope: types.Scope,
	props: {
		use: Fusion.Use,
		columnKey: number,
		columnData: types.flasherColumnDataState,
		columnPositions: Fusion.Value<{ [number]: { scope: any, value: Fusion.Value<Vector2> } }>,
		colors: Fusion.Value<{ chassisHandler.color }>,
		playing: Fusion.Value<boolean>,
		size: Fusion.Value<Vector2>,
		position: Fusion.Value<Vector2>,
		section: types.flasherSectionState,
		updateCurrentConfiguration: () -> (),
		propagateNameChange: (columnData: types.flasherColumnDataState, columnKey: number) -> (),
	}
)
	local currentTheme = RedonUI.theme.theme:now()

	local currentColumnPositions = props.use(props.columnPositions)
	if currentColumnPositions[props.columnKey] == nil then
		return {}
	end
	local color = scope:Computed(function(use)
		if use(props.playing) == false then
			return currentTheme.colors.base
		end
		local currentRow = use(props.section.currentRow)
		local colorData = scope.peek(props.colors)[scope.peek(props.columnData.rows)[currentRow]]
		return if colorData == nil
			then currentTheme.colors.base
			else if utils.bloomColors[colorData.name] ~= nil
				then utils.bloomColors[colorData.name]
				else colorData.lightoColor
	end)
	local baseProps = {
		Name = props.columnKey,
		Size = scope:Computed(function(use)
			return UDim2.new(0, 0.0401069519 * use(props.size).X, 1, 0)
		end),
		CornerRadius = UDim.new(0.1666666667, 0),
		Position = scope:Computed(function(use)
			return UDim2.new(
				0,
				use(currentColumnPositions[props.columnKey].value).X
					- use(props.position).X
					- 0.0133689840 * use(props.size).X,
				0,
				0
			)
		end),
		ZIndex = 2,
	}
	local descendants, children = {}, {}
	if props.use(settings.values.fakeBloom) then
		local visible = scope:Computed(function(use)
			return use(color) ~= currentTheme.colors.base
		end)
		baseProps.BackgroundColor3 = currentTheme.colors.base :: Fusion.UsedAs<Color3>

		children = {
			scope:base {
				Size = UDim2.fromScale(1, 1),
				BackgroundColor3 = Color3.new(1, 1, 1),
				CornerRadius = UDim.new(0.1666666667, 0),
				ZIndex = 2,
				Visible = visible,
			},
		}
		descendants = {
			scope:base {
				ClassName = "ImageLabel",
				Name = `{props.columnKey}Light`,
				AnchorPoint = Vector2.new(0, 0.5),
				BackgroundTransparency = 1,
				Image = assets.images.elsCreator.bloom,
				ImageColor3 = color,
				ScaleType = Enum.ScaleType.Slice,
				Size = scope:Computed(function(use)
					return UDim2.new(0, (0.0401069519 * use(props.size).X) + 25, 1, 25)
				end),
				Position = scope:Computed(function(use)
					return UDim2.new(
						0,
						use(currentColumnPositions[props.columnKey].value).X
							- use(props.position).X
							- 0.0133689840 * use(props.size).X
							- 12.5,
						0.5,
						0
					)
				end),
				SliceCenter = Rect.new(256, 256, 256, 256),
				Visible = visible,
			},

			scope:base {
				ClassName = "ImageLabel",
				Name = `{props.columnKey}Light1`,
				AnchorPoint = Vector2.new(0, 0.5),
				BackgroundTransparency = 1,
				Image = assets.images.elsCreator.bloom,
				ImageColor3 = color,
				ImageTransparency = 0.2,
				ScaleType = Enum.ScaleType.Slice,
				Size = scope:Computed(function(use)
					return UDim2.new(0, (0.0401069519 * use(props.size).X) + 150, 1, 150)
				end),
				Position = scope:Computed(function(use)
					return UDim2.new(
						0,
						use(currentColumnPositions[props.columnKey].value).X
							- use(props.position).X
							- 0.0133689840 * use(props.size).X
							- 75,
						0.5,
						0
					)
				end),
				SliceCenter = Rect.new(256, 256, 256, 256),
				Visible = visible,
			},

			scope:base {
				ClassName = "ImageLabel",
				Name = `{props.columnKey}Light2`,
				AnchorPoint = Vector2.new(0, 0.5),
				BackgroundTransparency = 1,
				Image = assets.images.elsCreator.bloom,
				ImageColor3 = color,
				ImageTransparency = 0.5,
				ScaleType = Enum.ScaleType.Slice,
				Size = scope:Computed(function(use)
					return UDim2.new(0, (0.0401069519 * use(props.size).X) + 200, 1, 200)
				end),
				Position = scope:Computed(function(use)
					return UDim2.new(
						0,
						use(currentColumnPositions[props.columnKey].value).X
							- use(props.position).X
							- 0.0133689840 * use(props.size).X
							- 100,
						0.5,
						0
					)
				end),
				SliceCenter = Rect.new(256, 256, 256, 256),
				Visible = visible,
			},
		}
	else
		baseProps.BackgroundColor3 = color
	end
	props.columnData.currentName = scope:Value(scope.peek(props.columnData.lightName))
	table.insert(
		children,
		1,
		scope:textBox {
			BackgroundTransparency = 1,
			Visible = scope:Computed(function(use)
				return use(props.playing) == false
			end),
			Size = UDim2.fromScale(1, 1),
			Text = props.columnData.lightName,
			[Fusion.Out "Text"] = props.columnData.lightName :: unknown,

			[Fusion.OnEvent "FocusLost"] = function(enterPressed: boolean)
				if enterPressed == false then
					props.updateCurrentConfiguration()
					return
				end
				props.propagateNameChange(props.columnData, props.columnKey)
				props.updateCurrentConfiguration()
			end,
		}
	)
	baseProps[Fusion.Children] = children

	table.insert(descendants, 1, scope:base(baseProps))
	return descendants
end


--#selene: allow(shadowing)
-- Redon Tech Emergency Vehicle Creator, MIT License
local root = script.Parent.Parent.Parent.Parent.Parent

local Fusion = require(root.Packages.fusion)
local RedonUI = require(root.Packages["redon-ui"])
local types = require(root.Plugin.types)
local popup = require(root.Plugin.Components.popup)
local chassisHandler = require(root.Plugin.chassisHandler)
local assets = require(root.Plugin.assets)
local log = require(root.Plugin.log)
local settings = require(root.Plugin.settings)
local utils = require(root.Plugin.utils)

local sectionsComponent = require(script.sections)
local components = {
	sectionsComponent = sectionsComponent,
}

type props = {
	UnsavedChanges: Fusion.Value<boolean>,
	Enabled: Fusion.UsedAs<boolean>,
	Mode: Fusion.Value<types.mode>,
	CurrentPanel: Fusion.Value<types.mainPanels>,
	CurrentPage: Fusion.Value<types.pages?>,
	CurrentVehicle: Fusion.Value<Model | string?>,
	CurrentPattern: Fusion.Value<types.selectedPattern?>,
}

--[[
	Flashers
	Creates the flashers screen for the main app
]]
return function(baseScope: types.Scope, props: props): Fusion.Child
	local currentTheme = RedonUI.theme.theme:now()
	local scope = baseScope:innerScope(components)
	local firstScope = scope:innerScope()

	local currentConfiguration: chassisHandler.chassisConfiguration? = nil
	local colors: Fusion.Value<{ chassisHandler.color }> = scope:Value(chassisHandler.defaults.colors)
	local currentColor = scope:Value(1)
	local drawing = scope:Value(0) -- -1 = remove, 0 = none, 1 = add
	local playing = scope:Value(false)
	local function temporarlyPause()
		local wasPlaying = scope.peek(playing)
		playing:set(false)
		return function()
			playing:set(wasPlaying)
		end
	end
	local locked = scope:Value(false)
	local holdingShift = scope:Value(false) -- This is used to skip certain popups
	local sections: Fusion.Value<{ types.flasherSectionState }> = scope:Value({})

	local function updateCurrentConfiguration()
		local currentPattern = scope.peek(props.CurrentPattern)
		if currentPattern ~= nil then
			local assembledSections = {}
			for i, section in scope.peek(sections) do
				local assembledSection: types.flasherSection = {
					waitTime = scope.peek(section.waitTime),
					rowCount = scope.peek(section.rowCount),
					columns = {},
				}
				for columnId, columnData in scope.peek(section.columns) do
					local assembledColumn = {} :: { lightName: string, rows: { types.elsColor } }
					assembledColumn.lightName = scope.peek(columnData.lightName)
					assembledColumn.rows = {}
					for _, color in scope.peek(columnData.rows) do
						table.insert(assembledColumn.rows, scope.peek(color))
					end
					assembledSection.columns[columnId] = assembledColumn
				end

				table.insert(assembledSections, i, assembledSection)
			end

			if utils.compareTables(currentPattern.pattern.flashers, assembledSections) == true then
				return
			end

			currentPattern.pattern.flashers = assembledSections
			props.UnsavedChanges:set(true)
		end
	end

	local function promptReset()
		popup:addPopup(scope, {
			title = "Reset Flashers",
			description = "Are you sure you want to reset all flashers?\n<b>All data will be lost.</b>",
			actions = {
				{
					text = "Cancel",
					type = "primary",
					key = 1,
					callback = function(newPopup)
						popup:removePopup(newPopup)
					end,
					keybind = Enum.KeyCode.Escape,
				},
				{
					text = "Reset",
					type = "danger",
					key = 2,
					callback = function(newPopup)
						popup:removePopup(newPopup)
						playing:set(false)
						sections:set({})
						locked:set(false)
						drawing:set(0)
						updateCurrentConfiguration()
					end,
				},
			},
		})
	end

	scope:Observer(props.CurrentPattern):onChange(function()
		local currentPattern = scope.peek(props.CurrentPattern)
		local currentVehicle = scope.peek(props.CurrentVehicle)
		if currentPattern ~= nil then
			playing:set(false)
			if typeof(currentVehicle) == "Instance" then
				currentConfiguration = chassisHandler:getConfiguration(currentVehicle)
				if currentConfiguration ~= nil then
					colors:set(currentConfiguration.lightSettings.colors)
				else
					colors:set(chassisHandler.defaults.colors)
				end
			end

			local flashers = currentPattern.pattern.flashers
			local newSections = {} :: { types.flasherSectionState }
			for i, section in flashers do
				local newColumns = {}
				for columnId, columnData in section.columns do
					local rows = {}
					for _, color in columnData.rows do
						table.insert(rows, color)
					end
					newColumns[columnId] = {
						lightName = firstScope:Value(columnData.lightName),
						rows = firstScope:Value(rows),
					}
				end
				table.insert(newSections, i, {
					waitTime = firstScope:Value(section.waitTime),
					rowCount = firstScope:Value(section.rowCount),
					currentRow = firstScope:Value(1),
					columns = firstScope:Value(newColumns),
				})
			end
			log.debug(newSections)
			sections:set(newSections)
		end
	end)

	local function addSection(atPoint: number?)
		local unpause = temporarlyPause()
		local currentSections = scope.peek(sections)
		if atPoint == nil then
			atPoint = #currentSections + 1
		end
		table.insert(currentSections, atPoint :: number, {
			waitTime = firstScope:Value(0.05),
			rowCount = firstScope:Value(10),
			currentRow = firstScope:Value(1),
			columns = firstScope:Value({
				{
					lightName = firstScope:Value("Light 1"),
					rows = firstScope:Value({
						0,
						0,
						0,
						0,
						0,
						0,
						0,
						0,
						0,
						0,
					}),
				},
			}),
		})
		sections:set(currentSections)
		updateCurrentConfiguration()
		unpause()
	end

	-- player
	local threads: { { activeThread: thread, sections: { number } } } = {}
	scope:Observer(playing):onChange(function()
		local isPlaying = scope.peek(playing)
		local currentSections = scope.peek(sections)
		if isPlaying == true then
			log.debug("Playing")
			for sectionId, section in currentSections do
				local waitTime = scope.peek(section.waitTime)
				if threads[waitTime] == nil then
					threads[waitTime] = {
						activeThread = task.spawn(function()
							while task.wait(waitTime) do
								for _, sectionId in pairs(threads[waitTime].sections) do
									local section = currentSections[sectionId]
									local nextRow = scope.peek(section.currentRow) + 1
									if nextRow > scope.peek(section.rowCount) then
										nextRow = 1
									end
									section.currentRow:set(nextRow)
								end
							end
						end),
						sections = {},
					}
				end

				table.insert(threads[waitTime].sections, sectionId)
			end
		else
			log.debug("Stopping")
			for i, thread in threads do
				coroutine.close(thread.activeThread)
				threads[i] = nil
			end
		end
	end)

	scope:Observer(props.Enabled):onChange(function()
		local enabled = scope.peek(props.Enabled)
		if enabled == false then
			playing:set(false)
		end
	end)

	scope:Observer(props.CurrentPage):onBind(function()
		local currentPage = scope.peek(props.CurrentPage)
		if currentPage ~= "Flashers" then
			playing:set(false)
		end
	end)

	local colorKeybinds = scope:Computed(function(use)
		local setting = use(settings.values.colorKeybinds)
		if setting == "1-6" then
			return {
				Enum.KeyCode.One,
				Enum.KeyCode.Two,
				Enum.KeyCode.Three,
				Enum.KeyCode.Four,
				Enum.KeyCode.Five,
				Enum.KeyCode.Six,
			}
		elseif setting == "NumPad 1-6" then
			return {
				Enum.KeyCode.KeypadOne,
				Enum.KeyCode.KeypadTwo,
				Enum.KeyCode.KeypadThree,
				Enum.KeyCode.KeypadFour,
				Enum.KeyCode.KeypadFive,
				Enum.KeyCode.KeypadSix,
			}
		else
			return {
				Enum.KeyCode.Q,
				Enum.KeyCode.W,
				Enum.KeyCode.E,
				Enum.KeyCode.R,
				Enum.KeyCode.T,
				Enum.KeyCode.Y,
			}
		end
	end)
	local playHovered = scope:Value(false)
	local lockedHovered = scope:Value(false)
	local size = scope:Value(Vector2.one)
	local sectionsTotalSpace = scope:Value(Vector2.one)
	local headerSize = scope:Value(Vector2.one)
	return scope:base {
		Name = "Flashers",
		AnchorPoint = Vector2.new(0.5, 0),
		Position = scope:Spring(
			scope:Computed(function(use)
				local currentPage = use(props.CurrentPage)
				if currentPage == "Flashers" then
					return UDim2.fromScale(0.5, 0.0462962963)
				end
				return UDim2.fromScale(-1, 0.0462962963)
			end),
			20,
			0.75
		),
		Size = UDim2.fromScale(1, 0.9537037037),
		BackgroundTransparency = 1,

		[Fusion.OnEvent "InputBegan"] = function(input: InputObject)
			if input.UserInputType == Enum.UserInputType.MouseButton1 then
				drawing:set(1)
			elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
				drawing:set(-1)
			elseif input.UserInputType == Enum.UserInputType.Keyboard then
				if input.KeyCode == Enum.KeyCode.Space or input.KeyCode == Enum.KeyCode.P then
					playing:set(not scope.peek(playing))
				elseif input.KeyCode == Enum.KeyCode.LeftShift then
					holdingShift:set(true)
				else
					local color = table.find(scope.peek(colorKeybinds), input.KeyCode)
					if color ~= nil then
						currentColor:set(color)
					end
				end
			end
		end :: unknown,

		[Fusion.OnEvent "InputEnded"] = function(input: InputObject)
			if
				input.UserInputType == Enum.UserInputType.MouseButton1
				or input.UserInputType == Enum.UserInputType.MouseButton2
			then
				drawing:set(0)
			elseif input.UserInputType == Enum.UserInputType.Keyboard then
				if input.KeyCode == Enum.KeyCode.LeftShift then
					holdingShift:set(false)
				end
			end
		end :: unknown,

		[Fusion.Children] = {
			scope:base {
				Name = "Header",
				AnchorPoint = Vector2.new(0, 0),
				Position = UDim2.fromScale(0, 0),
				Size = UDim2.fromScale(1, 0.0582524272),
				BackgroundColor3 = currentTheme.colors.mantle,

				[Fusion.Children] = {
					scope:base {
						ClassName = "ScrollingFrame",
						AnchorPoint = Vector2.new(0.5, 0.5),
						Position = UDim2.fromScale(0.5, 0.5),
						Size = UDim2.fromScale(1, 1),
						BackgroundTransparency = 1,
						CanvasSize = UDim2.new(),
						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.X,
						ClipsDescendants = false,

						[Fusion.Children] = Fusion.Child {
							scope:New("UIAspectRatioConstraint") {
								AspectRatio = 32,
							},

							scope:New "UIListLayout" {
								Padding = UDim.new(0.0078125, 0),
								FillDirection = Enum.FillDirection.Horizontal,
								HorizontalAlignment = scope:Computed(function(use)
									return use(headerSize).X > use(size).X and Enum.HorizontalAlignment.Left
										or Enum.HorizontalAlignment.Center
								end),
								VerticalAlignment = Enum.VerticalAlignment.Center,
								SortOrder = Enum.SortOrder.LayoutOrder,
								[Fusion.Out "AbsoluteContentSize"] = headerSize :: unknown,
							},

							scope:ForPairs(
								colors,
								function(_, scope: typeof(scope), key: number, value: chassisHandler.color)
									local color = utils.getColorByName(value.name, value.lightoColor)
									return key,
										scope:textButton {
											Size = UDim2.fromScale(0.078125, 0.8333333333),
											Text = value.name,
											TextScaled = true,
											MaxTextSize = 35,
											CornerRadius = UDim.new(0.2, 0),
											Border = 2,
											Reactive = true,
											BackgroundColor3 = scope:Computed(function(use)
												return if use(currentColor) == key
													then color
													else currentTheme.colors.mantle
											end),
											TextColor3 = scope:Computed(function(use)
												return if use(currentColor) == key
													then currentTheme.colors.mantle
													else color
											end),
											LayoutOrder = key,

											[Fusion.OnEvent "Activated"] = function()
												currentColor:set(key)
											end,
										}
								end
							),

							scope:iconButton {
								Size = UDim2.fromScale(0.0260416667, 0.8333333333),
								CornerRadius = UDim.new(0.2, 0),
								Border = 2,
								Reactive = true,
								LayoutOrder = 100,
								Image = scope:Computed(function(use)
									local isPlaying = use(playing)
									return if isPlaying
										then assets.images.redonUI.pause
										else if use(playHovered)
											then assets.images.redonUI.playFilled
											else assets.images.redonUI.play
								end),
								BackgroundColor3 = currentTheme.colors.mantle,
								ImageColor3 = currentTheme.colors.text,
								MouseEnter = function()
									playHovered:set(true)
								end,
								MouseLeave = function()
									playHovered:set(false)
								end,
								[Fusion.OnEvent "Activated"] = function()
									playing:set(not scope.peek(playing))
								end,
							},

							scope:iconButton {
								Size = UDim2.fromScale(0.0260416667, 0.8333333333),
								CornerRadius = UDim.new(0.2, 0),
								Border = 2,
								Reactive = true,
								LayoutOrder = 101,
								Image = scope:Computed(function(use)
									local hovering = use(lockedHovered)
									return if use(locked)
										then if hovering
											then assets.images.redonUI.lockClosedFilled
											else assets.images.redonUI.lockClosed
										else if hovering
											then assets.images.redonUI.lockOpenFilled
											else assets.images.redonUI.lockOpen
								end),
								BackgroundColor3 = currentTheme.colors.mantle,
								ImageColor3 = currentTheme.colors.text,
								MouseEnter = function()
									lockedHovered:set(true)
								end,
								MouseLeave = function()
									lockedHovered:set(false)
								end,
								[Fusion.OnEvent "Activated"] = function()
									locked:set(not scope.peek(locked))
								end,
							},

							scope:iconButton {
								Size = UDim2.fromScale(0.0260416667, 0.8333333333),
								CornerRadius = UDim.new(0.2, 0),
								Border = 2,
								Reactive = true,
								LayoutOrder = 102,
								Image = assets.images.redonUI.refresh,
								BackgroundColor3 = currentTheme.colors.mantle,
								ImageColor3 = currentTheme.colors.text,
								[Fusion.OnEvent "Activated"] = function()
									promptReset()
								end,
							},
						},
					},
				},
			},

			scope:base {
				Size = UDim2.fromScale(1, 1),
				AnchorPoint = Vector2.new(0.5, 1),
				Position = UDim2.fromScale(0.5, 1),
				BackgroundTransparency = 1,
				ClipsDescendants = true,

				[Fusion.Children] = {
					scope:New("UIAspectRatioConstraint") {
						AspectRatio = 1.9793814433,
					},

					scope:base {
						ClassName = "ScrollingFrame",
						Name = "Content",
						Size = UDim2.fromScale(0.9739583333, 0.9174757282), -- 1870, 945
						AnchorPoint = Vector2.new(0.5, 1),
						Position = UDim2.fromScale(0.5, 1),
						BackgroundTransparency = 1,
						CanvasSize = UDim2.new(),
						AutomaticCanvasSize = Enum.AutomaticSize.X,
						ScrollBarThickness = 3,
						ScrollBarImageColor3 = currentTheme.colors.text,
						TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						[Fusion.Out "AbsoluteSize"] = size :: unknown,
						ClipsDescendants = false,

						[Fusion.Children] = Fusion.Child {
							scope:New("UIListLayout") {
								Padding = UDim.new(0.0130208333, 0),
								FillDirection = Enum.FillDirection.Horizontal,
								HorizontalAlignment = scope:Computed(function(use)
									return if use(sectionsTotalSpace).X > use(size).X
										then Enum.HorizontalAlignment.Left
										else Enum.HorizontalAlignment.Center
								end),
								VerticalAlignment = Enum.VerticalAlignment.Top,
								SortOrder = Enum.SortOrder.LayoutOrder,
								[Fusion.Out "AbsoluteContentSize"] = sectionsTotalSpace :: unknown,
							},

							scope:sectionsComponent {
								colors = colors,
								currentColor = currentColor,
								playing = playing,
								drawing = drawing,
								size = size,
								temporarlyPause = temporarlyPause,
								locked = locked,
								holdingShift = holdingShift,
								sections = sections,
								updateCurrentConfiguration = updateCurrentConfiguration,
								addSection = addSection,
							},
						},
					},

					scope:textButton {
						Size = UDim2.fromScale(0.1822916667, 0.0776699029),
						AnchorPoint = Vector2.new(0.5, 0.5),
						Position = UDim2.fromScale(0.5, 0.5),
						BackgroundColor3 = Color3.new(1, 1, 1),
						Gradient = ColorSequence.new(currentTheme.colors.sky, currentTheme.colors.sapphire),
						CornerRadius = UDim.new(0.2, 0),
						Text = "Add Section",
						TextScaled = true,
						MaxTextSize = 40,
						TextColor3 = currentTheme.colors.mantle,
						Border = 2,
						Reactive = true,
						ButtonGlow = true,
						ButtonGlowColor3 = if currentTheme.colors.white == Color3.new(1, 1, 1)
							then currentTheme.colors.text
							else currentTheme.colors.base,
						Shadow = true,
						ShadowColor3 = currentTheme.colors.sky,
						Visible = scope:Computed(function(use)
							return #use(sections) == 0
						end),

						[Fusion.OnEvent "Activated"] = function()
							addSection()
						end,
					},
				},
			},
		},
	}
end