local l_script_FirstAncestor_0 = script:FindFirstAncestor("VisualizationModes");
local l_VisualizationModeService_0 = game:GetService("VisualizationModeService");
local v2 = require(l_script_FirstAncestor_0.Src.Resources.Localization.createLocalization);
local v3 = require(l_script_FirstAncestor_0.Packages.Framework);
local v4 = require(l_script_FirstAncestor_0.Src.Util.Maid);
local v5 = require(l_script_FirstAncestor_0.Packages.React);
local v6 = require(l_script_FirstAncestor_0.Packages.ReactRoblox);
local v7 = require(l_script_FirstAncestor_0.Src.Modes.StudioVisualizationModes);
local l_ContextServices_0 = v3.ContextServices;
local v9 = {
    ClassName = "StudioVisualizationModeProvider"
};
v9.__index = v9;
v9.new = function()
    local v10 = setmetatable({}, v9);
    v10._maid = v4.new();
    v10._localization = v2();
    l_VisualizationModeService_0:ClearAllChildren();
    v10._root = v6.createRoot(v10._maid:add(Instance.new("Folder")));
    v10._root:render(l_ContextServices_0.provide({
        v10._localization
    }, {
        StudioVisualizationModes = v6.createPortal(v5.createElement(v7, {
            Plugin = plugin
        }), l_VisualizationModeService_0)
    }));
    v10._maid:giveTask(function()
        v10._root:unmount();
    end);
    return v10;
end;
v9.destroy = function(v11)
    v11._maid:destroy();
end;
return v9;


local l_script_FirstAncestor_0 = script:FindFirstAncestor("VisualizationModes");
local v1 = require(l_script_FirstAncestor_0.Packages.Framework);
local v2 = require(l_script_FirstAncestor_0.Packages.React);
local v3 = require(l_script_FirstAncestor_0.Src.Modes.SettingVisualizationMode);
local v4 = require(l_script_FirstAncestor_0.Src.Flags.getFFlagDebugStudioShowBrokenVisualizationModes);
local l_ContextServices_0 = v1.ContextServices;
return function()
    local v6 = settings();
    local v7 = l_ContextServices_0.Localization:use();
    return v2.createElement(v2.Fragment, {}, {
        GUI = v2.createElement("VisualizationModeCategory", {
            Title = v7:getText("VisualizationModeCategories", "GUI")
        }, {
            DeviceEmulation = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "DeviceEmulation"),
                ToolTip = v7:getText("StudioModes", "DeviceEmulationToolTip")
            }),
            GUIOverlay = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "GUIOverlay"),
                ToolTip = v7:getText("StudioModes", "GUIOverlayToolTip"),
                Setting = game.StarterGui,
                Property = "ShowDevelopmentGui"
            }),
            GUIGuides = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "GUIGuides"),
                ToolTip = v7:getText("StudioModes", "GUIGuidesToolTip")
            })
        }),
        PhysicsGeometry = v2.createElement("VisualizationModeCategory", {
            Title = v7:getText("VisualizationModeCategories", "PhysicsGeometry")
        }, {
            AssemblyLabels = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "AssemblyLabels"),
                ToolTip = v7:getText("StudioModes", "AssemblyLabelsToolTip"),
                Setting = v6.Physics,
                Property = "AreAssembliesShown"
            }),
            DecompositionGeometry = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "DecompositionGeometry"),
                ToolTip = v7:getText("StudioModes", "DecompositionGeometryToolTip"),
                Setting = v6.Physics,
                Property = "ShowDecompositionGeometry"
            }),
            MechanismLabels = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "MechanismLabels"),
                ToolTip = v7:getText("StudioModes", "MechanismLabelsToolTip"),
                Setting = v6.Physics,
                Property = "AreMechanismsShown"
            }),
            UnanchoredPartLabels = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "UnanchoredPartLabels"),
                ToolTip = v7:getText("StudioModes", "UnanchoredPartLabelsToolTip"),
                Setting = v6.Physics,
                Property = "AreAnchorsShown"
            })
        }),
        Lighting = v2.createElement("VisualizationModeCategory", {
            Title = v7:getText("VisualizationModeCategories", "Lighting")
        }, {
            LightGuides = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "LightGuides"),
                ToolTip = v7:getText("StudioModes", "LightGuidesToolTip"),
                Setting = v6.Studio,
                Property = "Show Light Guides"
            })
        }),
        Navigation = v2.createElement("VisualizationModeCategory", {
            Title = v7:getText("VisualizationModeCategories", "Navigation")
        }, {
            NavigationMesh = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "NavigationMesh"),
                ToolTip = v7:getText("StudioModes", "NavigationMeshToolTip"),
                Setting = v6.Studio,
                Property = "Show Navigation Mesh"
            }),
            NavigationLabels = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "NavigationLabels"),
                ToolTip = v7:getText("StudioModes", "NavigationLabelsToolTip"),
                Setting = v6.Studio,
                Property = "Show Navigation Labels"
            }),
            PathfindingLinks = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "PathfindingLinks"),
                ToolTip = v7:getText("StudioModes", "PathfindingLinksToolTip"),
                Setting = v6.Studio,
                Property = "Show Pathfinding Links"
            })
        }),
        PhysicsConstraints = v2.createElement("VisualizationModeCategory", {
            Title = v7:getText("VisualizationModeCategories", "PhysicsConstraints")
        }, {
            Constraints = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "Constraints"),
                ToolTip = v7:getText("StudioModes", "ConstraintsToolTip")
            }),
            ConstraintsOnTop = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "ConstraintsOnTop"),
                ToolTip = v7:getText("StudioModes", "ConstraintsOnTopToolTip")
            }),
            ContactPoints = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "ContactPoints"),
                ToolTip = v7:getText("StudioModes", "ContactPointsToolTip")
            })
        }),
        PhysicsSimulation = v2.createElement("VisualizationModeCategory", {
            Title = v7:getText("VisualizationModeCategories", "PhysicsSimulation")
        }, {
            AwakeParts = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "HighlightAwakeParts"),
                ToolTip = v7:getText("StudioModes", "HighlightAwakePartsToolTip"),
                Setting = v6.Physics,
                Property = "AreAwakePartsHighlighted"
            }),
            NetworkOwner = v2.createElement(v3, {
                Title = v7:getText("StudioModes", "NetworkOwner"),
                ToolTip = v7:getText("StudioModes", "NetworkOwnerToolTip"),
                Setting = v6.Physics,
                Property = "AreOwnersShown"
            }),
            WindDirection = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "WindDirection"),
                ToolTip = v7:getText("StudioModes", "WindDirectionToolTip")
            })
        }),
        View = v2.createElement("VisualizationModeCategory", {
            Title = v7:getText("VisualizationModeCategories", "View")
        }, {
            ViewSelector = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "ViewSelector"),
                ToolTip = v7:getText("StudioModes", "ViewSelectorToolTip")
            }),
            CollaboratorHighlights = v4() and v2.createElement(v3, {
                Title = v7:getText("StudioModes", "CollaboratorHighlights"),
                ToolTip = v7:getText("StudioModes", "CollaboratorHighlightsToolTip")
            })
        })
    });
end;


local l_script_FirstAncestor_0 = script:FindFirstAncestor("VisualizationModes");
local l_ContextServices_0 = require(l_script_FirstAncestor_0.Packages.Framework).ContextServices;
local l_SourceStrings_0 = l_script_FirstAncestor_0.Src.Resources.Localization.SourceStrings;
local l_LocalizedStrings_0 = l_script_FirstAncestor_0.Src.Resources.Localization.LocalizedStrings;
return function()
    return (l_ContextServices_0.Localization.new({
        stringResourceTable = l_SourceStrings_0,
        translationResourceTable = l_LocalizedStrings_0,
        pluginName = "VisualizationModes"
    }));
end;


local v0 = require(script:FindFirstAncestor("VisualizationModes").Packages.Framework);
local l_createStyleRule_0 = v0.Styling.createStyleRule;
return v0.Styling.createStyleSheet("VisualizationModes", {
    l_createStyleRule_0(".VisualizationModes-Bottom-Divider", {
        Size = UDim2.new(1, 0, 0, 5),
        Position = UDim2.new(0.5, 0, 1, 0),
        AnchorPoint = Vector2.new(0.5, 1),
        BackgroundTransparency = 1
    }, {
        l_createStyleRule_0("> #DividerRender", {
            Size = UDim2.new(1, 0, 0, 1),
            Position = UDim2.fromOffset(0, 2),
            BackgroundColor3 = "$VisualizationModesDivider",
            BorderSizePixel = 0
        })
    }),
    l_createStyleRule_0(".VisualizationModes-TextLabel-Container", {
        BorderSizePixel = 0,
        BackgroundTransparency = 1,
        Size = UDim2.fromScale(0, 1),
        AutomaticSize = Enum.AutomaticSize.X
    }),
    l_createStyleRule_0(".VisualizationModes-Highlight", {
        BackgroundTransparency = 0,
        BorderSizePixel = 0,
        BackgroundColor3 = "$VisualizationModesHighlightColor",
        AnchorPoint = Vector2.new(0, 0.5),
        Position = UDim2.fromScale(0, 0.5)
    }),
    l_createStyleRule_0(".VisualizationModes-Highlight-TextLabel", {
        TextColor3 = "$VisualizationModesHighlightTextColor",
        AnchorPoint = Vector2.new(0, 0.5),
        Position = UDim2.fromScale(0, 0.5)
    }),
    l_createStyleRule_0(".VisualizationModes-TextLabel", {
        TextColor3 = "$TextPrimary",
        BorderSizePixel = 0,
        BackgroundTransparency = 1,
        TextXAlignment = Enum.TextXAlignment.Left,
        TextYAlignment = Enum.TextYAlignment.Center,
        Size = UDim2.fromScale(0, 1),
        AutomaticSize = Enum.AutomaticSize.X
    }, {
        l_createStyleRule_0(".VisualizationModes-Disabled", {
            TextColor3 = "$TextDisabled"
        })
    }),
    l_createStyleRule_0(".VisualizationModes-Badge", {
        BorderSizePixel = 0,
        BackgroundColor3 = "$VisualizationModesBadgeBackground"
    }, {
        l_createStyleRule_0("::UICorner", {
            CornerRadius = UDim.new(0.5, 0)
        }),
        l_createStyleRule_0("::UIPadding", {
            PaddingLeft = UDim.new(0, 6),
            PaddingTop = UDim.new(0, 1),
            PaddingBottom = UDim.new(0, 1),
            PaddingRight = UDim.new(0, 6)
        }),
        l_createStyleRule_0(">> .Component-TextLabel", {
            TextSize = 12,
            TextColor3 = "$VisualizationModesBadgeText",
            TextYAlignment = Enum.TextYAlignment.Center
        })
    }),
    l_createStyleRule_0(".VisualizationModes-MainPane", {}, {
        l_createStyleRule_0("::UIStroke", {})
    }),
    l_createStyleRule_0(".VisualizationModes-ExpandIcon", {
        BackgroundTransparency = 1,
        ImageColor3 = "$TextPrimary"
    }, {
        l_createStyleRule_0("ArrowImage", {
            Size = UDim2.fromOffset(16, 16),
            AnchorPoint = Vector2.new(0.5, 0.5),
            Position = UDim2.fromScale(0.5, 0.5)
        }),
        l_createStyleRule_0(":pressed", {
            ImageColor3 = "$TextSecondary"
        }),
        l_createStyleRule_0(":hover", {
            ImageColor3 = "$TextSecondary"
        })
    }),
    l_createStyleRule_0(".VisualizationModes-ExpandIcon-Expanded", {
        Image = "rbxasset://studio_svg_textures/Shared/Navigation/Dark/Standard/ArrowDown.png"
    }),
    l_createStyleRule_0(".VisualizationModes-ExpandIcon-Collapsed", {
        Image = "rbxasset://studio_svg_textures/Shared/Navigation/Dark/Standard/ArrowRight.png"
    }),
    l_createStyleRule_0(".VisualizationModes-Entry", {
        BackgroundTransparency = 1,
        Size = UDim2.new(1, 0, 0, 32),
        AutoButtonColor = false
    }, {
        l_createStyleRule_0("> #VisualizationModesEntryBacking", {
            BorderSizePixel = 0,
            Size = UDim2.new(1, 0, 0, 28),
            AnchorPoint = Vector2.new(0.5, 0.5),
            Position = UDim2.fromScale(0.5, 0.5),
            BackgroundTransparency = 1
        }, {
            l_createStyleRule_0("::UICorner", {
                CornerRadius = UDim.new(0, 4)
            })
        }),
        l_createStyleRule_0(".VisualizationModes-Active:pressed > #VisualizationModesEntryBacking", {
            BackgroundTransparency = 0,
            BackgroundColor3 = "$ActionActivated"
        }),
        l_createStyleRule_0(".VisualizationModes-Active:hover > #VisualizationModesEntryBacking", {
            BackgroundTransparency = 0,
            BackgroundColor3 = "$VisualizationModesDimActionHover"
        })
    }),
    l_createStyleRule_0(".ScrollingAutomaticSize", {
        AutomaticCanvasSize = Enum.AutomaticSize.Y,
        CanvasSize = UDim2.new(1, 0, 0, 20)
    }),
    l_createStyleRule_0(".VisualizationModes-EntryButton", {
        BorderSizePixel = 0,
        Size = UDim2.new(1, -20, 1, 0),
        BackgroundTransparency = 1,
        AutoButtonColor = false
    }),
    l_createStyleRule_0(".VisualizationModes-Group", {
        BorderSizePixel = 0,
        Size = UDim2.fromScale(1, 0),
        AutomaticSize = Enum.AutomaticSize.Y,
        BackgroundTransparency = 1,
        AutoButtonColor = false
    }),
    l_createStyleRule_0(".VisualizationModes-MainScrollingFrame", {
        ScrollBarThickness = 3
    }, {
        l_createStyleRule_0("> #Content", {
            Size = UDim2.new(1, -4, 0, 0)
        })
    }),
    l_createStyleRule_0(".VisualizationModes-Popup", {}, {
        l_createStyleRule_0("::UIPadding", {
            PaddingLeft = UDim.new(0, 5),
            PaddingBottom = UDim.new(0, 5),
            PaddingTop = UDim.new(0, 5),
            PaddingRight = UDim.new(0, 2)
        })
    }),
    l_createStyleRule_0(".VisualizationModesStrokePane", {
        Name = "UIStrokePane",
        BackgroundTransparency = 1,
        Size = UDim2.new(1, -2, 1, -2),
        AnchorPoint = Vector2.new(0.5, 0.5),
        Position = UDim2.fromScale(0.5, 0.5)
    }, {
        l_createStyleRule_0("::UIStroke", {
            Color = "$Divider"
        })
    }),
    l_createStyleRule_0(".VisualizationModes-HiddenCountLabel", {
        TextXAlignment = Enum.TextXAlignment.Center,
        TextYAlignment = Enum.TextYAlignment.Center,
        TextColor3 = "$TextSecondary",
        Size = UDim2.new(1, 0, 0, 28),
        BackgroundTransparency = 1
    })
}, {});


return require(script:FindFirstAncestor("VisualizationModes").Packages.Framework).Styling.createStyleSheet("VisualizationModesDarkTheme", {}, {
    VisualizationModesHighlightColor = "$Blue70",
    VisualizationModesHighlightTextColor = "$White",
    VisualizationModesDivider = "$Gray110",
    VisualizationModesDimActionHover = "$Gray80",
    VisualizationModesBadgeBackground = "$Gray70",
    VisualizationModesBadgeText = "$White"
});


return require(script:FindFirstAncestor("VisualizationModes").Packages.Framework).Styling.createStyleSheet("VisualizationModesLightTheme", {}, {
    VisualizationModesHighlightColor = "$Blue70",
    VisualizationModesDivider = "$Gray50",
    VisualizationModesHighlightTextColor = "$White",
    VisualizationModesDimActionHover = "$Gray30"
});


local l_script_FirstAncestor_0 = script:FindFirstAncestor("VisualizationModes");
local l_StudioService_0 = game:GetService("StudioService");
local v2 = require(l_script_FirstAncestor_0.Packages.Dash);
local _ = require(l_script_FirstAncestor_0.Src.Types);
return function(v4)
    local function v7(v5, v6)
        v4:SendEventDeferred("studio", "VisualizationModes", v5, (v2.join({
            studioSid = v4:GetSessionId(),
            clientId = v4:GetClientId(),
            placeId = game.PlaceId,
            userId = l_StudioService_0:GetUserId()
        }, v6)));
    end;
    return {
        openPlugin = function()
            v7("PluginOpen");
        end,
        visualizationModeToggled = function(_, v9)
            v7("VisualizationModeToggled", {
                actionSource = v9.actionSource,
                visualizationMode = v9.visualizationMode,
                visualizationModeCategory = v9.visualizationModeCategory,
                visualizationModeEnabled = v9.isEnabled
            });
        end,
        visualizationModeEnabledDuration = function(_, v11)
            v7("VisualizationModeEnabledDuration", {
                visualizationMode = v11.visualizationMode,
                visualizationModeCategory = v11.visualizationModeCategory,
                durationSeconds = v11.durationSeconds
            });
        end
    };
end;


local l_script_FirstAncestor_0 = script:FindFirstAncestor("VisualizationModes");
local v1 = require(l_script_FirstAncestor_0.Packages.React);
local v2 = require(l_script_FirstAncestor_0.Packages.Framework);
local v3 = require(l_script_FirstAncestor_0.Packages.Roact);
local v4 = require(l_script_FirstAncestor_0.Src.Resources.Localization.createLocalization);
local l_DocParser_0 = v2.Util.Typecheck.DocParser;
local l_ContextServices_0 = v2.ContextServices;
local l_StudioTheme_0 = v2.Style.Themes.StudioTheme;
local l_registerPluginStyles_0 = v2.Styling.registerPluginStyles;
local l_Pane_0 = v2.UI.Pane;
return {
    name = "Visualization Modes",
    exclude = {
        "_Index"
    },
    storyRoots = {
        l_script_FirstAncestor_0.Src.Stories,
        l_script_FirstAncestor_0.Src.Components
    },
    roact = v3,
    mapStory = function(v10)
        return function(v11)
            return l_ContextServices_0.provide({
                v4(),
                l_ContextServices_0.Mouse.new(v11.plugin:GetMouse()),
                l_ContextServices_0.Plugin.new(v11.plugin),
                l_StudioTheme_0.new()
            }, {
                Wrapper = v1.createElement(l_Pane_0, {
                    AutomaticSize = Enum.AutomaticSize.XY
                }, {
                    Child = v1.createElement(v10, v11),
                    StyleLink = v1.createElement("StyleLink", {
                        StyleSheet = l_registerPluginStyles_0(v11.plugin, l_script_FirstAncestor_0)
                    })
                })
            });
        end;
    end,
    mapDefinition = function(v12)
        local l_FirstChild_0 = v12.source.Parent:FindFirstChild(v12.name);
        if l_FirstChild_0 then
            local v14 = l_DocParser_0.new(v12.name, l_FirstChild_0);
            local l_status_0, l_result_0 = pcall(function()
                return v14:parse();
            end);
            if l_status_0 then
                v12.docs = l_result_0;
                v12.summary = l_result_0.Summary;
            end;
        end;
        return v12;
    end
};


local _ = require(script:FindFirstAncestor("VisualizationModes").Packages.React);
return {};


local v9 = {
    createConfig = function(v0)
        local v1 = true;
        if type(v0) ~= "table" then
            v1 = v0 == nil;
        end;
        assert(v1, "Bad config");
        v0 = v0 or {};
        if v0.caseSensitive == nil then
            v0.caseSensitive = false;
        elseif type(v0.caseSensitive) ~= "boolean" then
            error("Bad config.caseSensitive");
        end;
        v0.gapLeadingScore = v0.gapLeadingScore or -0.005;
        v0.gapTrailingScore = v0.gapTrailingScore or -0.005;
        v0.gapInnerScore = v0.gapInnerScore or -0.01;
        v0.consecutiveMatchScore = v0.consecutiveMatchScore or 1;
        v0.slashMatchScore = v0.slashMatchScore or 0.9;
        v0.wordMatchScore = v0.wordMatchScore or 0.8;
        v0.capitalMatchScore = v0.capitalMatchScore or 0.7;
        v0.dotMatchScore = v0.dotMatchScore or 0.6;
        v0.maxMatchLength = v0.maxMatchLength or 1024;
        return v0;
    end,
    isFzyConfig = function(v2)
        local v3 = false;
        if type(v2) == "table" then
            v3 = false;
            if type(v2.gapLeadingScore) == "number" then
                v3 = false;
                if type(v2.gapTrailingScore) == "number" then
                    v3 = false;
                    if type(v2.gapInnerScore) == "number" then
                        v3 = false;
                        if type(v2.consecutiveMatchScore) == "number" then
                            v3 = false;
                            if type(v2.slashMatchScore) == "number" then
                                v3 = false;
                                if type(v2.wordMatchScore) == "number" then
                                    v3 = false;
                                    if type(v2.capitalMatchScore) == "number" then
                                        v3 = false;
                                        if type(v2.dotMatchScore) == "number" then
                                            v3 = false;
                                            if type(v2.maxMatchLength) == "number" then
                                                v3 = type(v2.caseSensitive) == "boolean";
                                            end;
                                        end;
                                    end;
                                end;
                            end;
                        end;
                    end;
                end;
            end;
        end;
        return v3;
    end,
    hasMatch = function(v4, v5, v6)
        if not v4.caseSensitive then
            v5 = string.lower(v5);
            v6 = string.lower(v6);
        end;
        local v7 = 1;
        for v8 = 1, string.len(v5) do
            v7 = string.find(v6, v5:sub(v8, v8), v7, true);
            if v7 then
                v7 = v7 + 1;
            else
                return false;
            end;
        end;
        return true;
    end
};
local function _(v10)
    return string.match(v10, "%l");
end;
local function _(v12)
    return string.match(v12, "%u");
end;
local function v20(v14, v15)
    local v16 = {};
    local v17 = "/";
    for v18 = 1, string.len(v15) do
        local v19 = v15:sub(v18, v18);
        if not (v17 ~= "/") or v17 == "\\" then
            v16[v18] = v14.slashMatchScore;
        elseif not (v17 ~= "-" and v17 ~= "_") or v17 == " " then
            v16[v18] = v14.wordMatchScore;
        elseif v17 == "." then
            v16[v18] = v14.dotMatchScore;
        elseif not string.match(v17, "%l") or not string.match(v19, "%u") then
            v16[v18] = 0;
        else
            v16[v18] = v14.capitalMatchScore;
        end;
        v17 = v19;
    end;
    return v16;
end;
local function v37(v21, v22, v23, v24, v25)
    local v26 = v20(v21, v23);
    local v27 = string.len(v22);
    local v28 = string.len(v23);
    if not v21.caseSensitive then
        v22 = string.lower(v22);
        v23 = string.lower(v23);
    end;
    local v29 = {};
    for v30 = 1, v28 do
        v29[v30] = v23:sub(v30, v30);
    end;
    for v31 = 1, v27 do
        v24[v31] = {};
        v25[v31] = {};
        local v32 = -math.huge;
        local v33 = v31 == v27 and v21.gapTrailingScore or v21.gapInnerScore;
        local v34 = v22:sub(v31, v31);
        for v35 = 1, v28 do
            if v34 == v29[v35] then
                local v36 = -math.huge;
                if v31 == 1 then
                    v36 = (v35 - 1) * v21.gapLeadingScore + v26[v35];
                elseif v35 > 1 then
                    v36 = math.max(v25[v31 - 1][v35 - 1] + v26[v35], v24[v31 - 1][v35 - 1] + v21.consecutiveMatchScore);
                end;
                v24[v31][v35] = v36;
                v32 = math.max(v36, v32 + v33);
                v25[v31][v35] = v32;
            else
                v24[v31][v35] = -math.huge;
                v32 = v32 + v33;
                v25[v31][v35] = v32;
            end;
        end;
    end;
end;
v9.isPerfectMatch = function(v38, v39, v40)
    if not v38.caseSensitive then
        return string.lower(v39) == string.lower(v40);
    else
        return v39 == v40;
    end;
end;
v9.score = function(v41, v42, v43)
    local v44 = string.len(v42);
    local v45 = string.len(v43);
    if not ((v44 ~= 0 and v45 ~= 0) and v41.maxMatchLength >= v45) or v45 < v44 then
        return -math.huge;
    elseif not v9.isPerfectMatch(v41, v42, v43) then
        local v46 = {};
        local v47 = {};
        v37(v41, v42, v43, v46, v47);
        return v47[v44][v45];
    else
        return math.huge;
    end;
end;
v9.positions = function(v48, v49, v50)
    local v51 = string.len(v49);
    local v52 = string.len(v50);
    if not ((v51 ~= 0 and v52 ~= 0) and v48.maxMatchLength >= v52) or v52 < v51 then
        return {}, -math.huge;
    elseif not v9.isPerfectMatch(v48, v49, v50) then
        local v53 = {};
        local v54 = {};
        v37(v48, v49, v50, v53, v54);
        local v55 = {};
        local v56 = false;
        local l_v52_0 = v52;
        for v58 = v51, 1, -1 do
            while l_v52_0 >= 1 do
                if v53[v58][l_v52_0] ~= -math.huge and (v56 or v53[v58][l_v52_0] == v54[v58][l_v52_0]) then
                    local v59 = false;
                    if v58 ~= 1 then
                        v59 = false;
                        if l_v52_0 ~= 1 then
                            v59 = v54[v58][l_v52_0] == v53[v58 - 1][l_v52_0 - 1] + v48.consecutiveMatchScore;
                        end;
                    end;
                    v56 = v59;
                    v55[v58] = l_v52_0;
                    l_v52_0 = l_v52_0 - 1;
                    break;
                else
                    l_v52_0 = l_v52_0 - 1;
                end;
            end;
        end;
        return v55, v54[v51][v52];
    else
        local v60 = {};
        for v61 = 1, v51 do
            v60[v61] = v61;
        end;
        return v60, math.huge;
    end;
end;
v9.filter = function(v62, v63, v64)
    local v65 = {};
    for v66, v67 in ipairs(v64) do
        if v9.hasMatch(v62, v63, v67) then
            local v68, v69 = v9.positions(v62, v63, v67);
            table.insert(v65, {
                v66,
                v68,
                v69
            });
        end;
    end;
    return v65;
end;
v9.getMinScore = function()
    return -math.huge;
end;
v9.getMaxScore = function()
    return math.huge;
end;
v9.getMaxLength = function(v70)
    assert(v9.isFzyConfig(v70), "Bad config");
    return v70.maxMatchLength;
end;
v9.getScoreFloor = function(v71)
    assert(v9.isFzyConfig(v71), "Bad config");
    return v71.maxMatchLength * v71.gapInnerScore;
end;
v9.getScoreCeiling = function(v72)
    assert(v9.isFzyConfig(v72), "Bad config");
    return v72.maxMatchLength * v72.consecutiveMatchScore;
end;
return v9;


local v0 = {};
v0.new = function()
    return (setmetatable({
        _tasks = {}
    }, v0));
end;
v0.isMaid = function(v1)
    local v2 = false;
    if type(v1) == "table" then
        v2 = v1.ClassName == "Maid";
    end;
    return v2;
end;
v0.__index = function(v3, v4)
    if not v0[v4] then
        return v3._tasks[v4];
    else
        return v0[v4];
    end;
end;
v0.__newindex = function(v5, v6, v7)
    if v0[v6] ~= nil then
        error(string.format("Cannot use '%s' as a Maid key", (tostring(v6))), 2);
    end;
    local l__tasks_0 = v5._tasks;
    local v9 = l__tasks_0[v6];
    if v9 == v7 then
        return ;
    else
        l__tasks_0[v6] = v7;
        if v9 then
            if type(v9) == "function" then
                v9();
                return ;
            elseif type(v9) == "thread" then
                local v10 = nil;
                if coroutine.running() ~= v9 then
                    v10 = pcall(function()
                        task.cancel(v9);
                    end);
                end;
                if not v10 then
                    task.defer(function()
                        task.cancel(v9);
                    end);
                    return ;
                end;
            elseif typeof(v9) == "RBXScriptConnection" then
                v9:Disconnect();
                return ;
            elseif typeof(v9) == "Instance" then
                v9:Destroy();
                return ;
            elseif not v9.destroy then
                if v9.Destroy then
                    v9:Destroy();
                end;
            else
                v9:destroy();
                return ;
            end;
        end;
        return ;
    end;
end;
v0.add = function(v11, v12)
    if not v12 then
        error("Task cannot be false or nil", 2);
    end;
    v11[#v11._tasks + 1] = v12;
    if not ((not (type(v12) == "table") or v12.destroy) or v12.Destroy) then
        warn("[Maid.giveTask] - Gave table task without .Destroy\n\n" .. debug.traceback());
    end;
    return v12;
end;
v0.giveTask = function(v13, v14)
    if not v14 then
        error("Task cannot be false or nil", 2);
    end;
    local v15 = #v13._tasks + 1;
    v13[v15] = v14;
    if not ((not (type(v14) == "table") or v14.destroy) or v14.Destroy) then
        warn("[Maid.giveTask] - Gave table task without .Destroy\n\n" .. debug.traceback());
    end;
    return v15;
end;
v0.destroy = function(v16)
    local l__tasks_1 = v16._tasks;
    for v18, v19 in l__tasks_1, nil, nil do
        if typeof(v19) == "RBXScriptConnection" then
            l__tasks_1[v18] = nil;
            v19:Disconnect();
        end;
    end;
    local v20, v21 = next(l__tasks_1);
    while v21 ~= nil do
        l__tasks_1[v20] = nil;
        if type(v21) == "function" then
            v21();
        elseif type(v21) == "thread" then
            local v22 = nil;
            if coroutine.running() ~= v21 then
                v22 = pcall(function()
                    task.cancel(v21);
                end);
            end;
            if not v22 then
                local l_v21_0 = v21;
                local l_l_v21_0_0 = l_v21_0 --[[ copy: 5 -> 7 ]];
                task.defer(function()
                    task.cancel(l_l_v21_0_0);
                end);
            end;
        elseif typeof(v21) == "RBXScriptConnection" then
            v21:Disconnect();
        elseif typeof(v21) == "Instance" then
            v21:Destroy();
        elseif not v21.destroy then
            if v21.Destroy then
                v21:Destroy();
            end;
        else
            v21:destroy();
        end;
        local v25, v26 = next(l__tasks_1);
        v20 = v25;
        v21 = v26;
    end;
end;
return v0;


local v0 = nil;
local function v3(v1, ...)
    local l_v0_0 = v0;
    v0 = nil;
    v1(...);
    v0 = l_v0_0;
end;
local function v4()
    while true do
        v3(coroutine.yield());
    end;
end;
local v5 = {};
v5.__index = v5;
v5.new = function(v6, v7)
    return (setmetatable({
        _connected = true,
        _signal = v6,
        _fn = v7,
        _next = false
    }, v5));
end;
v5.Disconnect = function(v8)
    v8._connected = false;
    if v8._signal._handlerListHead == v8 then
        v8._signal._handlerListHead = v8._next;
        return ;
    else
        local l__handlerListHead_0 = v8._signal._handlerListHead;
        while l__handlerListHead_0 and l__handlerListHead_0._next ~= v8 do
            l__handlerListHead_0 = l__handlerListHead_0._next;
        end;
        if l__handlerListHead_0 then
            l__handlerListHead_0._next = v8._next;
        end;
        return ;
    end;
end;
v5.Destroy = v5.Disconnect;
v5.destroy = v5.Disconnect;
setmetatable(v5, {
    __index = function(_, v11)
        error(("Attempt to get Connection::%s (not a valid member)"):format((tostring(v11))), 2);
    end,
    __newindex = function(_, v13, _)
        error(("Attempt to set Connection::%s (not a valid member)"):format((tostring(v13))), 2);
    end
});
local v15 = {};
v15.__index = v15;
v15.new = function()
    return (setmetatable({
        _handlerListHead = false
    }, v15));
end;
v15.isSignal = function(v16)
    local v17 = false;
    if type(v16) == "table" then
        v17 = getmetatable(v16) == v15;
    end;
    return v17;
end;
v15.Connect = function(v18, v19)
    local v20 = v5.new(v18, v19);
    if not v18._handlerListHead then
        v18._handlerListHead = v20;
        return v20;
    else
        v20._next = v18._handlerListHead;
        v18._handlerListHead = v20;
        return v20;
    end;
end;
v15.DisconnectAll = function(v21)
    v21._handlerListHead = false;
end;
v15.Fire = function(v22, ...)
    local l__handlerListHead_1 = v22._handlerListHead;
    while l__handlerListHead_1 do
        if l__handlerListHead_1._connected then
            if not v0 then
                v0 = coroutine.create(v4);
                coroutine.resume(v0);
            end;
            task.spawn(v0, l__handlerListHead_1._fn, ...);
        end;
        l__handlerListHead_1 = l__handlerListHead_1._next;
    end;
end;
v15.Wait = function(v24)
    local v25 = coroutine.running();
    local v26 = nil;
    v26 = v24:Connect(function(...)
        v26:Disconnect();
        task.spawn(v25, ...);
    end);
    return coroutine.yield();
end;
v15.Once = function(v27, v28)
    local v29 = nil;
    v29 = v27:Connect(function(...)
        if v29._connected then
            v29:Disconnect();
        end;
        v28(...);
    end);
    return v29;
end;
v15.Destroy = v15.DisconnectAll;
v15.destroy = v15.DisconnectAll;
setmetatable(v15, {
    __index = function(_, v31)
        error(("Attempt to get Signal::%s (not a valid member)"):format((tostring(v31))), 2);
    end,
    __newindex = function(_, v33, _)
        error(("Attempt to set Signal::%s (not a valid member)"):format((tostring(v33))), 2);
    end
});
return v15;


local l_script_FirstAncestor_0 = script:FindFirstAncestor("VisualizationModes");
local v1 = require(l_script_FirstAncestor_0.Src.Resources.createAnalyticsHandlers);
local v2 = require(l_script_FirstAncestor_0.Src.Resources.Localization.createLocalization);
local v3 = require(l_script_FirstAncestor_0.Packages.Framework);
local v4 = require(l_script_FirstAncestor_0.Packages.React);
local v5 = require(l_script_FirstAncestor_0.Packages.ReactUtils);
local v6 = require(l_script_FirstAncestor_0.Src.Hooks.useSizeCalculator);
local v7 = require(l_script_FirstAncestor_0.Src.Hooks.useVisualizationModeCombiner);
local v8 = require(l_script_FirstAncestor_0.Src.Components.Actions.VisualizationModeActions);
local v9 = require(l_script_FirstAncestor_0.Src.Components.VisualizationModesMainView);
local v10 = require(l_script_FirstAncestor_0.Src.Flags.getFFlagStudioVisualizationModesUI);
local v11 = require(l_script_FirstAncestor_0.Src.Flags.getFFlagStudioPluginPopupAnchorPoint);
local l_UI_0 = v3.UI;
local l_PluginButton_0 = l_UI_0.PluginButton;
local l_PluginToolbar_0 = l_UI_0.PluginToolbar;
local l_Pane_0 = l_UI_0.Pane;
local l_Popup_0 = l_UI_0.Popup;
local l_ContextServices_0 = v3.ContextServices;
local l_StudioTheme_0 = v3.Style.Themes.StudioTheme;
local l_registerPluginStyles_0 = v3.Styling.registerPluginStyles;
local function v30(v20)
    local v21 = l_ContextServices_0.Localization:use();
    local v22 = l_ContextServices_0.Analytics:use();
    local v23 = l_ContextServices_0.Plugin:use():get();
    local v24 = v4.useRef(false);
    local v25 = v6({
        Width = 325,
        HeaderHeight = 32,
        MaxHeight = 700,
        InnerPadding = 5,
        Padding = Vector2.new(10, 10)
    });
    local v28 = v4.useState(function()
        local v26, v27 = l_registerPluginStyles_0(v23, l_script_FirstAncestor_0);
        return {
            design = v26,
            conn = v27
        };
    end, {
        v23
    });
    v4.useEffect(function()
        if v28.conn == nil then
            return nil;
        else
            return function()
                v28.conn:Disconnect();
            end;
        end;
    end, {
        v28.conn
    });
    local v29 = v4.useCallback(function()
        v20.ToggleState.disable();
    end);
    v4.useEffect(function()
        if v20.ToggleState.enabled then
            v22:report("openPlugin");
        end;
    end, {
        v22,
        v20.ToggleState.enabled
    });
    if v20.PluginButton then
        if v20.ToggleState.enabled or v24.current then
            return v4.createElement(l_Popup_0, {
                CreateWidgetImmediately = false,
                Enabled = v20.ToggleState.enabled,
                Modal = false,
                Title = v21:getText("Plugin", "Name"),
                Size = v25.size,
                OnClose = v29,
                PopupTarget = v20.PluginButton,
                PopupSelfAnchorPoint = if not v11() then nil else Vector2.new(1, 0),
                PopupTargetAnchorPoint = if not v11() then nil else Vector2.new(1, 1)
            }, {
                Wrapper = v4.createElement(l_Pane_0, {
                    [v4.Tag] = "X-Fill X-Column X-Top VisualizationModes-Popup"
                }, {
                    VisualizationModesMainView = v4.createElement(v9, {
                        VisualizationModeCategories = v20.CombinerState.state.categories,
                        RecentVisualizationModes = v20.CombinerState.state.recentModes,
                        SizeCalculator = v25,
                        OnVisualizationModeToggle = v20.CombinerState.onVisualizationModeToggle,
                        OnVisualizationModeCategoryToggle = v20.CombinerState.onVisualizationModeCategoryToggle
                    })
                }),
                VisualizationModesStrokePane = v4.createElement("Frame", {
                    [v4.Tag] = "VisualizationModesStrokePane"
                }),
                StyleLink = v4.createElement("StyleLink", {
                    StyleSheet = v28.design
                })
            });
        else
            return nil;
        end;
    else
        return nil;
    end;
end;
local function v37()
    local v31 = v5.useToggleState();
    local v32 = v4.useRef(nil);
    local v33 = l_ContextServices_0.Localization:use();
    local v34 = l_ContextServices_0.Plugin:use():get();
    local v35 = v7();
    if v10() then
        return v4.createElement(v4.Fragment, {}, {
            Actions = v4.createElement(v8, {
                VisualizationModeCategories = v35.state.categories,
                OnVisualizationModeToggle = v35.onVisualizationModeToggle
            }),
            Toolbar = v4.createElement(l_PluginToolbar_0, {
                Title = "visualizationModesToolbar",
                Plugin = v34,
                RenderButtons = function(v36)
                    return {
                        Toggle = v4.createElement(l_PluginButton_0, {
                            Toolbar = v36,
                            Active = v31.enabled,
                            Id = "visualizationModesButton",
                            Title = v33:getText("Plugin", "Button"),
                            Tooltip = v33:getText("Plugin", "Tooltip"),
                            Icon = "rbxlocaltheme://VisualizationModes",
                            OnClick = v31.toggle,
                            ClickableWhenViewportHidden = false,
                            ref = v32
                        })
                    };
                end
            }),
            Popup = v4.createElement(v30, {
                ToggleState = v31,
                CombinerState = v35,
                PluginButton = v32.current and v32.current.button
            })
        });
    else
        return nil;
    end;
end;
return function(v38)
    return l_ContextServices_0.provide({
        l_ContextServices_0.Plugin.new(v38.Plugin),
        l_ContextServices_0.Mouse.new(v38.Plugin:GetMouse()),
        v4.useState(function()
            return l_StudioTheme_0.new();
        end),
        v4.useState(function()
            return v2();
        end),
        (v4.useState(function()
            return l_ContextServices_0.Analytics.new(v1);
        end))
    }, {
        Main = v4.createElement(v37)
    });
end;


local color4 = require("./utils/color4")

return {
	colors = {
		Border = color4.create(160, 160, 175, 0.7),
		BorderDark = color4.create(110, 110, 125, 0.5),

		Text = color4.create(255, 255, 255, 1),
		TextDisabled = color4.create(128, 128, 128, 1),

		ResizeGrip = color4.create(66, 150, 250, 0.2),
		ResizeGripHovered = color4.create(66, 150, 250, 0.67),

		WindowBg = color4.create(15, 15, 15, 0.94),
		WindowBgCollapsed = color4.create(15, 15, 15, 0),

		TitleBg = color4.create(41, 74, 122, 1),
		TitleBgDisabled = color4.create(10, 10, 10, 1),
		TitleBgCollapsed = color4.create(0, 0, 0, 0.6),

        Menubar = color4.create(36, 36, 36, 1),

		Menu = color4.create(36, 36, 36, 1),
		MenuHovered = color4.create(41, 74, 122, 0.54),
		MenuSelected = color4.create(66, 150, 250, 0.4),

		MenuItem = color4.create(20, 20, 20, 0.94),
		MenuItemHovered = color4.create(66, 150, 250, 0.6),
		MenuItemActive = color4.create(15, 135, 250, 1),

		Button = color4.create(66, 150, 250, 0.4),
		ButtonHovered = color4.create(66, 150, 250, 0.6),
		ButtonActive = color4.create(15, 135, 250, 1),

		FrameBg = color4.create(41, 74, 122, 0.54),
		FrameBgHovered = color4.create(66, 150, 250, 0.54),
		FrameBgActive = color4.create(15, 135, 250, 0.67),

		Header = color4.create(66, 150, 250, 0.31),
		HeaderHovered = color4.create(66, 150, 250, 0.62),
		HeaderActive = color4.create(15, 135, 250, 1),

		Dropdown = color4.create(41, 74, 122, 0.54),
		DropdownHovered = color4.create(66, 150, 250, 0.54),

        Tooltip = color4.create(20, 20, 20, 0.94),
		Seperator = color4.create(99, 99, 99, 0.62),
		RadialCenter = color4.create(66, 150, 250, 1),

		Image = color4.create(255, 255, 255, 1),
	},
	assets = {
		rightPointingTriangle = "rbxasset://textures/DeveloperFramework/button_arrow_right.png",
		downPointingTriangle = "rbxasset://textures/DeveloperFramework/button_arrow_down.png",
		xCloseIcon = "rbxasset://textures/AnimationEditor/icon_close.png", -- best approximation for a close X which roblox supports, needs to be scaled about 2x
		bottomRightCorner = "rbxasset://textures/ui/InspectMenu/gr-item-selector-triangle.png", -- used in window resize icon in bottom right
		checkMark = "rbxasset://textures/AnimationEditor/icon_checkmark.png",
		placeholderImage = "rbxasset://textures/ui/GuiImagePlaceholder.png",
	},

	gizmo = {
		defaultSubdivision = 16,
	},

	font = Enum.Font.RobotoMono,
	minWindowSize = vector.create(200, 100),
	cornerRadius = 5,
	textSize = 14,
	rowHeight = 16,
	strokeThickness = 1.5,

	spacing = 8,
	padding = 4,

	input_width = 0.65, -- udim2 scale
	default_value_increment = 0.1,
	drag_delay = 0.3,
	drag_sensitivity = 10,

	dropdown_width = 0.7, -- udim2 scale

	menu_item_width = 75,
}


return {
	shouldDefault = true,
	default = function(self: any, instance: Instance, applier: (Instance, any, any) -> ())
		if self.shouldDefault then
			local default = self[instance.ClassName] :: any

			if not default then return end

			for key, value in default do
				(instance::any)[key] = value
			end
		end
	end,

	Frame = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0
	},

	ImageButton = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0,
		AutoButtonColor = false
	},

	ImageLabel = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0,
	},

	ScreenGui = {
		ResetOnSpawn = false,
		ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	},

	ScrollingFrame = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0,
		ScrollBarImageColor3 = Color3.new(0, 0, 0)
	},

	SurfaceGui = {
		ResetOnSpawn = false,
		ZIndexBehavior = Enum.ZIndexBehavior.Sibling,

		PixelsPerStud = 50,
		SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud
	},

	TextBox = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0,
		ClearTextOnFocus = false,
		Font = Enum.Font.SourceSans,
		Text = "",
		TextColor3 = Color3.new(0, 0, 0)
	},

	TextButton = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0,
		AutoButtonColor = false,
		Font = Enum.Font.SourceSans,
		Text = "",
		TextColor3 = Color3.new(0, 0, 0)
	},

	TextLabel = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0,
		Font = Enum.Font.SourceSans,
		Text = "",
		TextColor3 = Color3.new(0, 0, 0)
	},

	UIListLayout = {
		SortOrder = Enum.SortOrder.LayoutOrder
	},

	UIGridLayout = {
		SortOrder = Enum.SortOrder.LayoutOrder
	},

	UITableLayout = {
		SortOrder = Enum.SortOrder.LayoutOrder
	},

	UIPageLayout = {
		SortOrder = Enum.SortOrder.LayoutOrder
	},

	VideoFrame = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0
	},

	ViewportFrame = {
		BackgroundColor3 = Color3.new(1, 1, 1),
		BorderColor3 = Color3.new(0, 0, 0),
		BorderSizePixel = 0
	}
}


local root = require("../utils/rootkit")
local gizmo = require("../utils/gizmo")

type vec3 = gizmo.vec3
type arguments = {
	transform: CFrame,
	size: vec3,
}

local edges = {
	{1, 2}, {2, 3}, {3, 4}, {4, 1},
	{5, 6}, {6, 7}, {7, 8}, {8, 5},

	{1, 5}, {2, 6}, {3, 7}, {4, 8}
}

return gizmo.create("box", function(arguments: arguments)
	local args = root.default(arguments, {}) :: arguments

	local transform = args.transform
	local size = args.size
	local halfSize = size :: Vector3 / 2

	local points = {
		Vector3.new(-halfSize.X, -halfSize.Y, -halfSize.Z), -- 1
		Vector3.new( halfSize.X, -halfSize.Y, -halfSize.Z), -- 2
		Vector3.new( halfSize.X, -halfSize.Y,  halfSize.Z), -- 3
		Vector3.new(-halfSize.X, -halfSize.Y,  halfSize.Z), -- 4

		Vector3.new(-halfSize.X,  halfSize.Y, -halfSize.Z), -- 5
		Vector3.new( halfSize.X,  halfSize.Y, -halfSize.Z), -- 6
		Vector3.new( halfSize.X,  halfSize.Y,  halfSize.Z), -- 7
		Vector3.new(-halfSize.X,  halfSize.Y,  halfSize.Z)  -- 8
	}

	for _, edge in edges do
		local start = transform * points[edge[1]]
		local close = transform * points[edge[2]]
		gizmo.draw_line(start, close)
	end
end)


local config = require("../config")
local root = require("../utils/rootkit")
local gizmo = require("../utils/gizmo")

type arguments = {
	radius: number,
	transform: CFrame,

	subdivisions: number?,
	angle: number?,
	close_shape: boolean?,
}

return gizmo.create("circle", function(arguments: arguments)
	local args = root.default(arguments, {
		subdivisions = config.gizmo.defaultSubdivision,
		angle = 360,
	}) :: root.dissolve<arguments>

	args.angle %= 361
	if args.angle < 360 then
		args.close_shape = false
	else
		args.close_shape = true
	end

	local angle_step = args.angle / args.subdivisions
	local total_angle = 0

	local vertices = {}
	for i=1, args.subdivisions do
		local px = math.sin( math.rad(total_angle) ) * args.radius
		local py = math.cos( math.rad(total_angle) ) * args.radius

		local vertex = args.transform:PointToWorldSpace( Vector3.new(px, 0, py) )

		table.insert(vertices, vertex)
		total_angle += angle_step
	end

	gizmo.draw_path(vertices, args.close_shape)
end)


local config = require("../config")
local root = require("../utils/rootkit")
local gizmo = require("../utils/gizmo")

local circle = require("./circle")

type arguments = {
	transform: CFrame,
	radius: number,
	height: number,
	subdivisions: number?,
}

return gizmo.create("cone", function(arguments: arguments)
	local args = root.default(arguments, {
        subdivisions = config.gizmo.defaultSubdivision,
    }) :: root.dissolve<arguments>

    local tip = args.transform * Vector3.new(0, args.height, 0)

	local angle_step = 360 / args.subdivisions
	local total_angle = 0

    for i=1, args.subdivisions do
		local px = math.sin( math.rad(total_angle) ) * args.radius
		local py = math.cos( math.rad(total_angle) ) * args.radius

        gizmo.draw_line(
            tip,
            args.transform * Vector3.new(px, 0, py)
        )
        total_angle += angle_step
    end

    circle {
        transform = args.transform,
        radius = args.radius,
        subdivisions = args.subdivisions,
        angle = 360,
    }
end)


local config = require("../config")
local root = require("../utils/rootkit")
local gizmo = require("../utils/gizmo")

local circle = require("./circle")

type vec3 = gizmo.vec3
type arguments = {
	transform: CFrame,
	radius: number,
	height: number,
	subdivisions: number?,
}

return gizmo.create("cylinder", function(arguments: arguments)
	local args = root.default(arguments, {
		subdivisions = config.gizmo.defaultSubdivision,
	}) :: root.dissolve<arguments>

	local angle_step = 360 / args.subdivisions
    local half_height = Vector3.yAxis * (args.height / 2)
	local total_angle = 0

	for i = 1, args.subdivisions do
		local px = math.sin( math.rad(total_angle) ) * args.radius
		local py = math.cos( math.rad(total_angle) ) * args.radius
        local p = Vector3.new(px, 0, py)

        gizmo.draw_line(
            args.transform * (p + half_height),
            args.transform * (p - half_height)
        )
		total_angle += angle_step
	end

    local transfrom_top = args.transform * CFrame.new(half_height)
    local transform_bottom = args.transform * CFrame.new(-half_height)

    circle {
        transform = transfrom_top,
        radius = args.radius,
        subdivisions = args.subdivisions,
        angle = 360,
    }

    circle {
        transform = transform_bottom,
        radius = args.radius,
        subdivisions = args.subdivisions,
        angle = 360,
    }
end)


local root = require("../utils/rootkit")
local gizmo = require("../utils/gizmo")

type vec3 = gizmo.vec3
type arguments = {
	origin: vec3,
	target: vec3,
}

return gizmo.create("line", function(arguments: arguments)
	local args = root.default(arguments, {}) :: arguments
	gizmo.draw_line(
		args.origin :: Vector3,
		args.target :: Vector3
	)
end)


local root = require("../utils/rootkit")
local gizmo = require("../utils/gizmo")

type vec3 = gizmo.vec3
type arguments = {
	points: { vec3 },
	close: boolean?,
}

return gizmo.create("polyline", function(arguments: arguments)
	local args = root.default(arguments, {}) :: arguments
	gizmo.draw_path(
		args.points :: { Vector3 },
		args.close
	)
end)


local config = require("../config")
local root = require("../utils/rootkit")
local gizmo = require("../utils/gizmo")

local circle = require("./circle")

type arguments = {
	radius: number,
	transform: CFrame,
	subdivisions: number?,
}

return gizmo.create("sphere", function(arguments: arguments)
	local args = root.default(arguments, {
		subdivisions = config.gizmo.defaultSubdivision,
	}) :: root.dissolve<arguments>

	circle({
		radius = args.radius,
		transform = args.transform,
		subdivisions = args.subdivisions,
	})

	circle({
		radius = args.radius,
		transform = args.transform * CFrame.Angles(math.rad(90), 0, 0),
		subdivisions = args.subdivisions,
	})

	circle({
		radius = args.radius,
		transform = args.transform * CFrame.Angles(0, 0, math.rad(90)),
		subdivisions = args.subdivisions,
	})
end)


local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")

local internal = require(script.internal)

export type props = internal.props
export type source<T> = internal.source<T>

local function get_target()
	local player = Players.LocalPlayer
	local player_gui = player and player:FindFirstChildOfClass("PlayerGui")

	assert(player_gui, "playergui not found")

	if player_gui:FindFirstChild("jello_root") then
		return player_gui:FindFirstChild("jello_root") :: ScreenGui
	end

	local root = Instance.new("ScreenGui")
	root.IgnoreGuiInset = false
	root.ResetOnSpawn = false
	root.DisplayOrder = 1000
	root.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	root.Name = "jello_root"
	root.Parent = player_gui
	return root
end

local function error_handler(message: string)
	internal._paused = true
	warn(message)
	print(debug.traceback())

	for i=1, internal._profile_count do
		internal.endprofile()
	end
end

local widgets = script.widgets
local gizmos = script.gizmos

local datatypes = {
	number = require(widgets.datatypes.number),
	vector2 = require(widgets.datatypes.vector2),
	vector3 = require(widgets.datatypes.vector3),
	udim = require(widgets.datatypes.udim),
	udim2 = require(widgets.datatypes.udim2),
	color3 = require(widgets.datatypes.color3),
	rect = require(widgets.datatypes.rect),
}

local jello = {
	update_frequency = -1,
	version = "0.1.0",
	_initialized = nil :: boolean?,
	_target = nil :: Instance?,

	-- input widgets
	number = datatypes.number.base,
	vector2 = datatypes.vector2.base,
	vector3 = datatypes.vector3.base,
	udim = datatypes.udim.base,
	udim2 = datatypes.udim2.base,
	color3 = datatypes.color3.base,
	rect = datatypes.rect.base,

	-- formatting widgets
	row = require(widgets.format.row),
	seperator = require(widgets.format.seperator),
	group = require(widgets.format.group),
	indent = require(widgets.format.indent),
	section = require(widgets.format.section),
	header = require(widgets.format.header),

	-- widgets
	window = require(widgets.window),
	text = require(widgets.text),
	checkbox = require(widgets.checkbox),
	button = require(widgets.button),
	radiobutton = require(widgets.radiobutton),
	textinput = require(widgets.textinput),
	tooltip = require(widgets.tooltip),

	dropdown = require(widgets.dropdown),
	option = require(widgets.option),

	menubar = require(widgets.menu.menubar),
	menuitem = require(widgets.menu.menuitem),
	menu = require(widgets.menu.menu),

	-- gizmos
	line = require(gizmos.line),
	polyline = require(gizmos.polyline),
	circle = require(gizmos.circle),
	box = require(gizmos.box),
	sphere = require(gizmos.sphere),
	cone = require(gizmos.cone),
	cylinder = require(gizmos.cylinder),
}

function jello.create(class_name: string)
	return function(props: props)
		local id = internal.gen_id(2, class_name)

		internal.define_object(id, class_name, props)
		return internal.render(id)
	end
end

function jello.open_scope(target: Instance)
	internal.push_parent(target)
end

function jello.close()
	internal.pop_parent()
end

function jello.scope(fn: () -> ())
	internal.scope(fn)
end

function jello.connect(fn: () -> ()): () -> ()
	return internal.listen(fn)
end

function jello.source<T>(default: T): source<T>
	local id = internal.gen_id(1, "source")
	return internal.source(default, id)
end

function jello.step()
	if internal._paused then
		internal.clear_instances()
		return
	end

	internal.profile("mount-listeners")

	local listener_count = 0
	for listener in internal._listeners do
		internal.profile(`listener-{listener_count}`)
		listener()
		internal.endprofile()

		listener_count+=1
	end

	internal.endprofile()

	local parent_stack_length = internal._parent_stack_length
	internal.assert(parent_stack_length <= 0, "too few calls to jello.close")
	internal.assert(parent_stack_length >= 0, "too many calls to jello.close")

	internal._tick += 1

	internal.profile("cleanup")
	internal.cleanup_frame()
	internal.endprofile()

	internal.profile("reset")
	internal.reset_framedata()
	internal.endprofile()
end

type init_args = {
	parent: Instance?,
	no_loop: boolean?,
	no_config: boolean?,
	no_gizmo: boolean?,
	frequency: number?,
}

function jello.init(args: init_args?)
	if jello._initialized :: any then return jello end

	local parent = args and args.parent
	local no_init = args and args.no_loop
	local no_config = args and args.no_config
	local no_gizmo = args and args.no_gizmo
	local frequency = args and args.frequency

	local target = parent or get_target()
	internal.update_target(target)

	if no_gizmo ~= true then
		local gizmo_util = require("@self/utils/gizmo")
		gizmo_util.init()
	end

	if no_init ~= true then
		local focused = true
		local accumulator = 0

		UserInputService.WindowFocusReleased:Connect(function() focused = false end)
		UserInputService.WindowFocused:Connect(function() focused = true end)

		RunService.Heartbeat:Connect(function(delta_time)
			if focused == false then return end

			while accumulator >= jello.update_frequency do
				internal.profile("step")

				xpcall(jello.step :: any, error_handler)
				accumulator -= jello.update_frequency

				internal.endprofile()
			end

			accumulator += delta_time
		end)
	end

	if no_config ~= true then
		local object_count = 0
		local last_update = -1
		internal.update_config(function(instance: Instance, props: props)
			if last_update ~= internal._tick then
				last_update = internal._tick
				object_count = 0
			end
			object_count += 1

			if instance:IsA("GuiObject") and props.LayoutOrder == nil then
				instance.LayoutOrder = object_count
			end

			local id = tostring(object_count)
			instance.Name = id
		end)
	end

	jello.update_frequency = if frequency then frequency else 1/30
	jello._target = target

	return jello
end

return jello


type id = string

type object<P=props> = {
	class_name: string,
	properties: P,
}

type listeners = { [string]: { (Instance, ...any) -> ()}}
type actions = { [(Instance) -> ()]: true }
export type props = { [any]: any } & { any }
export type source<T> = (T) -> () & () -> (T)

local defaults = require("./defaults")

local instances: { [id]: Instance } = {}
local instance_to_id = {} :: { [Instance]: string }
local instance_metadata: { [Instance]: {
	connections: { [any]: RBXScriptConnection },
	listeners: listeners,
	actions: actions,
} } = {}

local enviroment: { [string]: any } = { root = {} }

local function throw(message: string?, level: number?)
	error(`[jello]: {message}`, (level or 0) + 1)
end

local hash_cache = {}
local function djb_hash(str: string)
	if hash_cache[str] then return hash_cache[str] end

	local hash: number = 5381
	for i = 1, #str do
		local char_code = string.byte(str, i)
		local hash_times_33 = bit32.lshift(hash, 5) + hash
		hash = hash_times_33 + char_code
	end

	local result = tostring(bit32.band(hash, 0xFFFFFFFF))
	hash_cache[str] = result
	return result
end

local function assert(condition: any, message: string?, level: number?)
	if not condition then throw(message, (level or 1) + 1) end
end

local function read_property(instance: Instance, key: any): any?
	local success, result = pcall(function()
		return (instance :: any)[key]
	end)

	assert(success, `"{key}" is not a valid property of "{instance.ClassName}"`)
	return result
end

local function write_property(instance: Instance, key: any, value: any)
	local success, result = pcall(function()
		(instance :: any)[key] = value
		return nil
	end)

	assert(success, `failed to write_property "{key}" to "{instance.ClassName}": {result}`)
end

local function get_value<T>(t: T | source<T>): T
	if typeof(t) == "function" then return (t::source<T>)() end
	return t
end

local function get_trash(instance: Instance)
	if instance_metadata[instance] then return instance_metadata[instance] end

	local trash = {
		connections = {},
		listeners = {},
		actions = {},
	}
	instance_metadata[instance] = trash
	return trash
end

local function merge_listeners(target: listeners, source: listeners)
	for event in source do
		local from = source[event]
		local to = target[event]

		if to ~= nil then
			table.move(from, 1, #from, #to + 1, to)
		else
			target[event] = from
		end
	end
end

local function process_properties(instance: Instance, props: props): (props, listeners, actions)
	local result_props: props = {}
	local result_events: listeners = {}
	local result_actions: actions = {}

	for key: any, value: any in props do
		if typeof(key) == "string" then
			if typeof(read_property(instance, key)) == "RBXScriptSignal" then
				local listeners = result_events[key] or {}
				table.insert(listeners, value)
				result_events[key] = listeners
			else
				result_props[key] = get_value(value)
			end
		elseif typeof(value) == "table" then
			local nested_props, nested_events = process_properties(instance, value :: props)
			result_props[key] = nested_props
			merge_listeners(result_events, nested_events)
		elseif typeof(value) == "function" then
			result_actions[value] = true
		else
			result_props[key] = value
		end
	end

	return result_props, result_events, result_actions
end

local function get_property_changes(instance: Instance, a_props: props?, b_props: props): props?
	local changed_props = {}
    a_props = (a_props or {}) :: props

	for key in b_props do
		local a_value = a_props[key]
		local b_value = b_props[key]

		local is_function = (type(a_value) == "function" or type(b_value) == "function")
		if type(key) == "string" and is_function then continue end

		if type(a_value) == "table" and type(b_value) == "table" then
			local a, b = (a_value :: props), (b_value :: props)
			changed_props[key] = get_property_changes(instance, a, b)
		elseif a_value ~= b_value then
			changed_props[key] = b_value :: any
		end
	end

	return if next(changed_props) then changed_props else nil
end

local function construct_object(class_name: string)
	local object = Instance.new(class_name)
	defaults:default(object, write_property)
	get_trash(object)
	return object
end

local function destruct_object(id: id)
	local instance = instances[id]
	if instance :: Instance? == nil then return end

	instance:Destroy()

	local metadata = instance_metadata[instance]
	for _, connection in metadata.connections do
		connection:Disconnect()
	end

	for name, listeners in metadata.listeners do
		if name ~= "Destroying" then continue end
		for _, cn in listeners do task.spawn(cn, instance) end
	end

	instance_metadata[instance] = nil
	instance_to_id[instance] = nil
	instances[id] = nil
end

local function update_connections(instance: Instance, new_listeners: listeners)
	local trash = get_trash(instance)

	for event_name, connection in trash.connections do
		if new_listeners[event_name] ~= nil then continue end

		connection:Disconnect()
		trash.connections[event_name] = nil
	end

	for event_name in new_listeners do
		if trash.connections[event_name] ~= nil then continue end

		local event_signal = read_property(instance, event_name)
		if typeof(event_signal) ~= "RBXScriptSignal" then continue end

		trash.connections[event_name] = event_signal:Connect(function(...)
			local connections = trash.listeners[event_name]
			if connections == nil then return end

			for _, listener in connections do
				task.spawn(listener, instance, ...)
			end
		end)
	end

	trash.listeners = new_listeners
end

local internal = {
	_env = "root",
	_debug = false,
	_paused = false,

	_tick = 0,

	_instances = instances,
	_instance_to_id = instance_to_id,
	_used_ids = {} :: { [id]: number },
	_pushed_ids = {} :: { id },
	_vdom = {} :: { [id]: object },
	_last_vdom = {} :: { [id]: object },
	_instance_metadata = instance_metadata,

	_parent_stack = {} :: { Instance },
	_parent_stack_length = 0,
	_target = nil :: Instance?,

	_listeners = {} :: { [() -> ()]: true },
	_config = nil :: ((Instance, props) -> ())?,
	_profile_count = 0,

	assert = assert,
	throw = throw,
	read = get_value,
	read_property = read_property,
	write_property = write_property,

	verbose = false,
}

function internal.profile(name: string)
	if internal._debug == false then return end
	debug.profilebegin(`jello::{name}`)

	internal._profile_count += 1
end

function internal.endprofile()
	if internal._debug == false then return end
	if internal._profile_count < 1 then return end

	internal._profile_count -= 1
	debug.profileend()
end

function internal.log(message: string, warning: boolean?)
	if internal._debug == false then return end

	local method = if warning then warn else print
	method(`[jello]: {message}`)
end

function internal.gen_id(ignore_levels: number?, prefix: string?): string
	ignore_levels = 1 + ( ignore_levels or 0 )
	prefix = prefix or "rbx-id"

	local line_number = debug.info(ignore_levels, "l")

	assert(
		line_number ~= nil,
		`could not generate a stable id, call stack is not deep enough (requested level: {ignore_levels})`
	)

	local ID = ""

	while line_number ~= -1 and line_number ~= nil do
		ID = `{ID}:{line_number}`
		ignore_levels += 1
		line_number = debug.info(ignore_levels, "l")
	end

	if not internal.verbose then
		ID = djb_hash(ID)
	end

	local pushed_ids = table.concat(internal._pushed_ids, "-")
	local discriminator = internal._used_ids[ID] or 0
	discriminator += 1
	internal._used_ids[ID] = discriminator

	local footer =
		if #pushed_ids == 0 then `[{discriminator}]`
		else `[{discriminator}, {pushed_ids}]`

	if internal.verbose then
		return `{prefix}({ID}): {footer}`
	else
		return `jello("{ID}", {footer})`
	end
end

function internal.push_id(id: any)
	table.insert(internal._pushed_ids, id)
end

function internal.pop_id()
	table.remove(internal._pushed_ids)
end

function internal.setenv(id: string, env: { [any]: any })
	enviroment[id] = env
	internal._env = id
end

function internal.getenv(name: string?)
	name = name or internal._env
	return enviroment[name]
end

function internal.set(key: string, value: any)
	internal.getenv()[key] = value
end

function internal.get(key: string)
	return internal.getenv()[key]
end

function internal.source<T>(default: T?, idx: string?): source<T>
	local id = idx or internal.gen_id(1, "source")
	local value =
		if internal.get(id) ~= nil then internal.get(id)
		else default

	internal.set(id, value)
	return function(...: any): any
		if select("#", ...) ~= 0 then
			return internal.set(id, ...)
		end
		return internal.get(id)
	end
end

function internal.define_object(id: id, class_name: string, properties: props)
	internal._vdom[id] = {
		class_name = class_name,
		properties = properties,
	}

	local instance = instances[id]
	if instance :: Instance? == nil then
		instance = construct_object(class_name)
		instances[id] = instance
	end

	if internal._config then
		internal._config(instance, properties)
	end

	instance_to_id[instance] = id
end

function internal.apply(instance: Instance, properties: props)
	local trash = get_trash(instance)

	for property, value in properties do
		if type(property) == "string" then
			write_property(instance, property, get_value(value))
		elseif type(property) == "number" then
			if (type)(value) == "function" then
				trash.actions[value] = true
			elseif (type)(value) == "table" then
				internal.apply(instance, value)
			elseif typeof(value) == "Instance" then
				write_property(value, "Parent", instance)
			end
		end
	end

	return instance
end

function internal.render(id: id)
	internal.profile("render")

	local object = internal._vdom[id]
	local last_object = internal._last_vdom[id]
	local instance = instances[id]

	local props, listeners, actions = process_properties(instance, object.properties)
	update_connections(instance, listeners)

	local last_props = last_object and last_object.properties
	local changes = get_property_changes(instance, last_props, props)
	if changes then internal.apply(instance, changes) end

	for action in actions do
		action(instance)
	end

	object.properties = props

	internal.endprofile()
	return instance
end

function internal.cleanup_frame()
	for id, obj in internal._last_vdom do
		if internal._vdom[id] ~= nil then continue end
		destruct_object(id)
	end

	table.clear(internal._last_vdom)
end

function internal.reset_framedata()
	for id, object in internal._vdom do
		internal._last_vdom[id] = object
	end

	table.clear(internal._vdom)
	table.clear(internal._pushed_ids)
	table.clear(internal._used_ids)
end

function internal.listen(callback: () -> ())
	internal._listeners[callback] = true
	return function()
		internal._listeners[callback] = nil
	end
end

function internal.update_config(fn: (instance: Instance, props: props) -> ())
	internal._config = fn
end

function internal.clear_instances()
	for id in instances do
		destruct_object(id)
	end
end

function internal.push_parent(parent: Instance)
	internal._parent_stack_length += 1
	table.insert(internal._parent_stack, parent)
end

function internal.pop_parent(depth: number?)
	internal._parent_stack_length -= 1

	local index = #internal._parent_stack - (depth or 0)
	table.remove(internal._parent_stack, index)
end

function internal.update_target(target: Instance)
	internal._target = target
end

function internal.get_parent()
	return internal._parent_stack[internal._parent_stack_length] or internal._target
end

function internal.mount(object: Instance, target: (Instance | "root")?)
	if target == "root" then
		target = internal._target
	end

	target = target or internal.get_parent()
	write_property(object, "Parent", target)
end

function internal.scope(fn: () -> ())
	internal.assert(
		internal._parent_stack_length > 0,
		"attempt to create scope without a parent"
	)

	local success, result = pcall(fn::any)
	internal.pop_parent()

	if not success then internal.throw(result) end
end

function internal.create(class: string, id: string?)
	return function(props: props): Instance
		local id = id or internal.gen_id(2, "create")
		internal.define_object(id, class, props)
		return internal.render(id)
	end
end

return internal


local color4 = {}

export type color4 = buffer

function color4.create(r: number?, g: number?, b: number?, a: number?): color4
	r, g, b, a = r or 0, g or 0, b or 0, a or 1

	local color = buffer.create(4)
	buffer.writeu8(color, 0, r)
	buffer.writeu8(color, 1, g)
	buffer.writeu8(color, 2, b)
	buffer.writeu8(color, 3, math.round(a * 255))
	return color
end

function color4.read_color3(color: color4)
	return Color3.new(
		buffer.readu8(color, 0) / 255,
		buffer.readu8(color, 1) / 255,
		buffer.readu8(color, 2) / 255
	)
end

function color4.read_alpha(color: color4)
	return 1 - (buffer.readu8(color, 3) / 255)
end

function color4.read(color: color4)
	return color4.read_color3(color), color4.read_alpha(color)
end

function color4.is_color4(value: any)
	return
		typeof(value) == "buffer" and
		buffer.len(value) == 4
end

return color4


local root = require("./rootkit")

local adornments = {
	adornment = nil :: WireframeHandleAdornment?,
	aot_adornment = nil :: WireframeHandleAdornment?,
}

local last_update = -1
local state_stack = {}

local default_state = {
	color = Color3.new(0.937255, 0.0392157, 1),
	transparency = 0,
	always_on_top = false,
	target = workspace.Terrain,
}

type partial<T> = root.partial<T>

export type vec3 = Vector3 | vector
export type vec2 = Vector2

type state = typeof(default_state)

local function get_state()
	return state_stack[#state_stack]
end

local function get_adornment(): WireframeHandleAdornment
	local state = get_state()
	local result =
		if state.always_on_top then adornments.aot_adornment
		else adornments.adornment

	root.assert(result, "attempt to use gizmos without intitalization")
	return result :: WireframeHandleAdornment
end

local function reconcile(target, key, value: unknown)
	if target[key] == value then return end
	target[key] = value
end

local function reconcile_state(adornment: WireframeHandleAdornment)
	local state = get_state()

	reconcile(adornment, "Color3", state.color)
	reconcile(adornment, "Transparency", state.transparency)
	reconcile(adornment, "AlwaysOnTop", state.always_on_top)

	reconcile(adornment, "Parent", state.target)
	reconcile(adornment, "Adornee", state.target)
end

local function push_state(override: partial<state>)
	local state = table.clone( get_state() )

	for k, v in override :: any do
		if typeof(state[k]) ~= typeof(v) then continue end
		state[k] = v
	end

	table.insert(state_stack, state)
	reconcile_state( get_adornment() )
end

local function pop_state()
	root.assert(#state_stack > 1, "too many calls to pop_state")

	table.remove(state_stack)
	reconcile_state( get_adornment() )
end

local function update()
	if last_update == root.get_tick() then return end
	last_update = root.get_tick()

	local adornment = get_adornment()
	reconcile_state(adornment)

	adornment:Clear()
end

local function draw_line(origin: Vector3, target: Vector3)
	update()
	local adornment = get_adornment()
	adornment:AddLine(origin, target)
end

local function draw_path(points: { Vector3 }, loop: boolean?)
	update()
	local adornment = get_adornment()
	adornment:AddPath(points, loop or false)
end

local function draw_text(point: Vector3, text: string, size: number?)
	update()
	local adornment = get_adornment()
	adornment:AddText(point, text, size)
end

local function init()
	local adornment = Instance.new("WireframeHandleAdornment")
	local aot_adornment = Instance.new("WireframeHandleAdornment")

	adornment.AlwaysOnTop = false
	aot_adornment.AlwaysOnTop = true

	adornments = {
		adornment = adornment,
		aot_adornment = aot_adornment,
	}

	local state = table.clone(default_state)
	table.insert(state_stack, state)

	reconcile_state(adornment)
	reconcile_state(aot_adornment)
end

local function create<T>(name: string, constructor: (T) -> ())
	local gizmo_id = root.gen_id(1, `gizmo::{name}`)

	return function(arguments: T)
		push_state(arguments)

		root.push_id(gizmo_id)
		constructor(arguments)
		root.pop_id()

		pop_state()
	end
end

return {
	draw_line = draw_line,
	draw_path = draw_path,
	draw_text = draw_text,

	init = init,
	create = create,

	push_state = push_state,
	pop_state = pop_state,
}


local UserInputService = game:GetService("UserInputService")

local root = require("./rootkit")
local paint = require("./paint")
local ui = require("./uikit")
local config = require("../config")

local row = require("../widgets/format/row")

type source<T> = root.source<T>
type value<T> = root.value<T>

export type DataType = "number" | "Vector2" | "Vector3" | "vector" | "UDim" | "UDim2" | "Color3" | "buffer" | "Rect" | string
export type options = {
	prefix: string?,
	align: ("Left" | "Center" | "Right" | string)?,

	id_suffix: string?,

	min: number?,
	max: number?,
	increment: number?,
	decimal_places: number?,

	has_buttons: boolean?,
	has_color_preview: boolean?,
}

type component_options = {
	width: UDim,
	value_source: source<number>,
	on_change: (new_value: number) -> (),
}

type increment_options = {
	value_source: source<number>,
	increment_by: number,
	on_change: ((incremented: boolean) -> ())?,
}

local DATA_TYPES = { "number", "Vector2", "Vector3", "vector", "UDim", "UDim2", "Color3", "buffer", "Rect" }
local DATA_TYPE_SIZES = { number = 1, Vector2 = 2, Vector3 = 3, vector = 3, UDim = 2, UDim2 = 4, Color3 = 3, buffer = 4, Rect = 4 }
local DRAG_PRECISION = 12

local BUTTON_SIZE = config.rowHeight + config.padding

local read_handlers: { [DataType]: (any, number) -> any } = {
	number = function(value: number, index: number): number
		return value
	end,
	Vector2 = function(value: Vector2, index: number): number
		if index == 1 then return value.X end
		if index == 2 then return value.Y end
		error("Index out of range")
	end,
	Vector3 = function(value: Vector3, index: number): number
		if index == 1 then return value.X end
		if index == 2 then return value.Y end
		if index == 3 then return value.Z end
		error("Index out of range")
	end,
	vector = function(value: vector, index: number): number
		if index == 1 then return value.x end
		if index == 2 then return value.y end
		if index == 3 then return value.z end
		error("Index out of range")
	end,
	UDim = function(value: UDim, index: number): number
		if index == 1 then return value.Scale end
		if index == 2 then return value.Offset end
		error("Index out of range")
	end,
	UDim2 = function(value: UDim2, index: number): number
		if index == 1 then return value.X.Scale end
		if index == 2 then return value.X.Offset end
		if index == 3 then return value.Y.Scale end
		if index == 4 then return value.Y.Offset end
		error("Index out of range")
	end,
	Rect = function(value: Rect, index: number): number
		if index == 1 then return value.Min.X end
		if index == 2 then return value.Min.Y end
		if index == 3 then return value.Max.X end
		if index == 4 then return value.Max.Y end
		error("Index out of range")
	end,
	Color3 = function(value: Color3, index: number): number
		if index == 1 then return value.R * 255 end
		if index == 2 then return value.G * 255 end
		if index == 3 then return value.B * 255 end
		error("Index out of range")
	end,
	Color3_HSV = function(value: Color3, index: number): number
		local h, s, v = value:ToHSV()
		if index == 1 then return h * 255 end
		if index == 2 then return s * 255 end
		if index == 3 then return v * 255 end
		error("Index out of range")
	end,
	buffer = function(value: buffer, index: number): number
		return buffer.readu8(value, index - 1)
	end,
}

local write_handlers: { [DataType]: (any, number, any) -> any } = {
	number = function(target: number, index: number, value: number): number
		return value
	end,
	Vector2 = function(target: Vector2, index: number, value: number): Vector2
		if index == 1 then return Vector2.new(value, target.Y) end
		if index == 2 then return Vector2.new(target.X, value) end
		error("Index out of range")
	end,
	Vector3 = function(target: Vector3, index: number, value: number): Vector3
		if index == 1 then return Vector3.new(value, target.Y, target.Z) end
		if index == 2 then return Vector3.new(target.X, value, target.Z) end
		if index == 3 then return Vector3.new(target.X, target.Y, value) end
		error("Index out of range")
	end,
	vector = function(target: vector, index: number, value: number): { x: number, y: number, z: number }
		if index == 1 then return vector.create(value, target.y, target.z) end
		if index == 2 then return vector.create(target.x, value, target.z) end
		if index == 3 then return vector.create(target.x, target.y, value) end
		error("Index out of range")
	end,
	UDim = function(target: UDim, index: number, value: number): UDim
		if index == 1 then return UDim.new(value, target.Offset) end
		if index == 2 then return UDim.new(target.Scale, value) end
		error("Index out of range")
	end,
	UDim2 = function(target: UDim2, index: number, value: number): UDim2
		if index == 1 then return UDim2.new(value, target.X.Offset, target.Y.Scale, target.Y.Offset) end
		if index == 2 then return UDim2.new(target.X.Scale, value, target.Y.Scale, target.Y.Offset) end
		if index == 3 then return UDim2.new(target.X.Scale, target.X.Offset, value, target.Y.Offset) end
		if index == 4 then return UDim2.new(target.X.Scale, target.X.Offset, target.Y.Scale, value) end
		error("Index out of range")
	end,
	Rect = function(target: Rect, index: number, value: number): Rect
		if index == 1 then return Rect.new(value, target.Min.Y, target.Max.X, target.Max.Y) end
		if index == 2 then return Rect.new(target.Min.X, value, target.Max.X, target.Max.Y) end
		if index == 3 then return Rect.new(target.Min.X, target.Min.Y, value, target.Max.Y) end
		if index == 4 then return Rect.new(target.Min.X, target.Min.Y, target.Max.X, value) end
		error("Index out of range")
	end,
	Color3 = function(target: Color3, index: number, value: number): Color3
		if index == 1 then return Color3.fromRGB(value, target.G * 255, target.B * 255) end
		if index == 2 then return Color3.fromRGB(target.R * 255, value, target.B * 255) end
		if index == 3 then return Color3.fromRGB(target.R * 255, target.G * 255, value) end
		error("Index out of range")
	end,
	Color3_HSV = function(target: Color3, index: number, value: number): Color3
		local h, s, v = target:ToHSV()
		if index == 1 then return Color3.fromHSV(value / 255, s, v) end
		if index == 2 then return Color3.fromHSV(h, value / 255, v) end
		if index == 3 then return Color3.fromHSV(h, s, value / 255) end
		error("Index out of range")
	end,
	buffer = function(target: buffer, index: number, value: number): buffer
		buffer.writeu8(target, index - 1, value)
		return target
	end,
}

local DEFAULTS = {
	prefixes = {
		number = { "" },
		Vector2 = { "x: ", "y: " },
		Vector3 = { "x: ", "y: ", "z: " },
		UDim = { "", "" },
		UDim2 = { "", "", "", "" },
		Color3 = { "r: ", "g: ", "b: " },
		Color3_HSV = { "h: ", "s: ", "v: " },
		Rect = { "", "", "", "" },
	},
	decimal_places = {
		number = { 0 },
		Vector2 = { 0, 0 },
		Vector3 = { 0, 0, 0 },
		UDim = { 2, 0 },
		UDim2 = { 2, 0, 2, 0 },
		Color3 = { 0, 0, 0 },
		Color3_HSV = { 0, 0, 0 },
		Rect = { 0, 0, 0, 0 },
	},
}

local function is_valid_data_type(value: any)
	return table.find(DATA_TYPES, typeof(value)) ~= nil
end

local function read_component_value<T>(target: T, index: number, suffix: string?): number
	root.assert(is_valid_data_type(target), "invalid data type for reading component value")

	local handler_id = (typeof(target) :: DataType) .. (suffix or "")
	local handler = read_handlers[handler_id]

	if handler then
		return handler(target, index)
	end

	error(`unknown data type or index for read: {handler_id}, {index}`)
end

local function write_component_value<T>(target: T, index: number, value: any, suffix: string?): T
	root.assert(is_valid_data_type(target), "invalid data type for writing component value")

	local handler_id = (typeof(target) :: DataType) .. (suffix or "")
	local handler = write_handlers[handler_id]

	if handler then
		return handler(target, index, value)
	end

	error(`unknown data type or index for write: {handler_id}, {index}`)
end

local function get_component_count(value: any): number
	root.assert(is_valid_data_type(value), "invalid data type for getting component count")
	return DATA_TYPE_SIZES[typeof(value) :: DataType]
end

local function get_default(category: string, item: string?)
	local result = DEFAULTS[category]
	if result and item then
		result = (result :: any)[item]
	end
	return result
end

local function truncate_number(value: number, decimal_places: number)
	local multiplier = 10 ^ decimal_places
	return math.floor(value * multiplier) / multiplier
end

local function apply_modifiers(value: number, options: options)
	return math.clamp(value, options.min or -math.huge, options.max or math.huge)
end

local function create_input_component(params: component_options, options: options?)
	local resolved_options = options or {} :: options
	local decimal_places = resolved_options.decimal_places
	local text_align = Enum.TextXAlignment:FromName(resolved_options.align or "Left")
	local prefix = resolved_options.prefix or ""

	local value_source = params.value_source
	local on_change = params.on_change

	local function format_text()
		local formatted_value = value_source()
		formatted_value = string.format(`%.{decimal_places}f` :: any, formatted_value)
		return `{prefix}{formatted_value}`
	end

	local is_active = root.source(false)
	local drag_connection: RBXScriptConnection?
	local ended_connection: RBXScriptConnection?

	local textbox = root.create("TextBox") {
		function(self: TextBox)
			is_active(not root.keys_pressed("LeftControl", "RightControl"))
		end,

		AutomaticSize = Enum.AutomaticSize.Y,
		ClearTextOnFocus = false,
		TextXAlignment = text_align,
		TextTruncate = Enum.TextTruncate.None,

		ClipsDescendants = true,

		Active = is_active,
		Text = format_text,
		Size = UDim2.new(params.width, UDim.new(0, 0)),

		paint.text(),
		paint.background(),
		ui.padding(config.padding),

		Focused = function(self: TextBox)
			self.CursorPosition = #self.Text + 1
			self.SelectionStart = #prefix + 1
		end,

		FocusLost = function(self: TextBox)
			local text_to_parse = self.Text
			if prefix ~= "" and text_to_parse:sub(1, #prefix) == prefix then
				text_to_parse = text_to_parse:sub(#prefix + 1)
			end

			local new_value = tonumber(text_to_parse)
			local precision = decimal_places or DRAG_PRECISION

			if
				new_value and
				truncate_number(new_value, precision) == new_value and
				apply_modifiers(new_value, resolved_options) == new_value
			then
				on_change(apply_modifiers(new_value, resolved_options))
			end
		end,

		InputBegan = function(self: TextBox, input: InputObject)
			if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
			if drag_connection then return end
			if is_active() then return end

			self:ReleaseFocus()

			local start_mouse_position = input.Position
			local start_value = value_source()
			drag_connection = UserInputService.InputChanged:Connect(function(input: InputObject)
				if input.UserInputType ~= Enum.UserInputType.MouseMovement then return end

				local delta_x = start_mouse_position.X - input.Position.X
				local precision = decimal_places or DRAG_PRECISION
				local new_value = truncate_number(start_value + delta_x / config.drag_sensitivity, precision)
				on_change(apply_modifiers(new_value, resolved_options))
			end)

			ended_connection = UserInputService.InputEnded:Connect(function(input: InputObject)
				if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
				if drag_connection ~= nil then
                    drag_connection:Disconnect()
                    drag_connection = nil
				end

                if ended_connection ~= nil then
                    ended_connection:Disconnect()
                    ended_connection = nil
                end
			end)
		end,
	}

	if textbox:IsA("TextBox") and not textbox:IsFocused() then
		textbox.Text = format_text()
	end

	return {
		object = textbox,
	}
end

local function create_button(text: string, on_click: (self: GuiObject, x: number, y: number) -> ())
	local clicked = root.event()
	local hovered = root.source(false)

	return root.create("TextLabel") {
		Active = true,
		Text = text,
		TextXAlignment = Enum.TextXAlignment.Center,
		Size = UDim2.fromOffset(BUTTON_SIZE, BUTTON_SIZE),

		paint.text(),
		paint.button(clicked.fired, hovered),

		ui.hovered(hovered),
		ui.clicked("Button1", function(self, x, y)
			clicked.fire()
			on_click(self, x, y)
		end),
	}
end

local function create_increment(params: increment_options)
	assert(params.increment_by > 0, "increment must be greater than 0")
	assert(is_valid_data_type(params.value_source()), "invalid datatype passed to create_increment")

	local function on_button_click(is_add: boolean)
		local current_value = params.value_source()
		local increment = params.increment_by * (if is_add then 1 else -1)
		local new_value = truncate_number(current_value + increment, DRAG_PRECISION)

		params.value_source(new_value)
		if params.on_change then params.on_change(is_add) end
	end

	local add_button = create_button("+", function() on_button_click(true) end)
	local subtract_button = create_button("-", function() on_button_click(false) end)

	ui.mount(function()
        return root.create("Frame") {
            AutomaticSize = Enum.AutomaticSize.XY,
            BackgroundTransparency = 1,

            add_button,
            subtract_button,

            ui.list_layout("Horizontal", config.spacing),
        }
    end)
end

local function create_color_preview(color_source: value<Color3>, transparency_source: value<number>?)
	local object = root.create("Frame") {
		Size = UDim2.fromOffset(BUTTON_SIZE, BUTTON_SIZE),
		BackgroundColor3 = root.read(color_source),
		BackgroundTransparency = root.read(transparency_source or 0),
	}

	ui.mount(object)
	return object
end

local function get_value_at_index(collection: any, index: number)
	if typeof(collection) == "table" then
		return (collection :: any)[index]
	end
	return collection
end

local function get_buttons_width(has_increment_buttons: boolean?, has_color_preview: boolean?)
	local button_count = 0
	if has_increment_buttons then button_count += 2 end
	if has_color_preview then button_count += 1 end
	return (BUTTON_SIZE + config.spacing) * button_count
end

local function create_data_type_inputs(value: root.value<any>, options: options?)
	local raw_value = root.read(value)
	local data_type = typeof(raw_value)
	local resolved_options = root.default(options, {}) :: options
	local suffix = resolved_options.id_suffix or ""
	local data_type_id = data_type .. suffix

	local tracked_suffix = root.from(suffix)
	local suffix_changed = tracked_suffix() ~= suffix
	if suffix_changed then
		tracked_suffix(suffix)
	end

	local prefixes = resolved_options.prefix or get_default("prefixes", data_type_id)
	local min = resolved_options.min
	local max = resolved_options.max
	local increment = resolved_options.increment
	local decimal_places = resolved_options.decimal_places or get_default("decimal_places", data_type_id)

	local component_count = get_component_count(raw_value)
	local buttons_width = get_buttons_width(resolved_options.has_buttons, resolved_options.has_color_preview)
	local component_width = UDim.new(
		config.input_width / component_count,
		-(config.spacing + (buttons_width / component_count))
	)

	local components = {}
	local target_source = root.from(value)
	local changed = root.event()

	for i = 1, component_count do
		local component_value = root.from(read_component_value(raw_value, i, suffix))

		if root.source_changed(target_source) or suffix_changed then
			local updated_component_value = read_component_value(root.read(target_source), i, suffix)
			component_value(updated_component_value)
		end

		local function on_component_change(new_value: number)
			local new_target_value = write_component_value(root.read(target_source), i, new_value, suffix)
			target_source(new_target_value)
			changed.fire()
		end

		local component_options: options = {
			align = resolved_options.align or "Left",
			min = min,
			max = max,
			prefix = get_value_at_index(prefixes, i),
			decimal_places = get_value_at_index(decimal_places, i),
		}

		local component_params: component_options = {
			width = component_width,
			value_source = component_value,
			on_change = on_component_change,
		}

		local component = create_input_component(component_params, component_options)
		ui.mount(component.object)

		table.insert(components, {
			object = component.object,
			value = component_value,
		})
	end

	if resolved_options.has_buttons and increment and #components > 0 then
		local first_component_source = components[1].value
		local increment_params: increment_options = {
			value_source = first_component_source,
			increment_by = increment,
			on_change = function()
				local new_parent_value = write_component_value(
					root.read(target_source), 1,
					first_component_source(), suffix
				)
				target_source(new_parent_value)
				changed.fire()
			end,
		}

		create_increment(increment_params)
	end

	if resolved_options.has_color_preview and data_type == "Color3" then
		create_color_preview(target_source :: source<Color3>)
	end

	return {
		changed = root.derive(changed.fired),
		value = target_source,
	}
end

local function create_input_label(text: string)
	ui.mount(function()
		return root.create("TextLabel") {
			Text = text,
			AutomaticSize = Enum.AutomaticSize.Y,
			TextXAlignment = Enum.TextXAlignment.Left,
			Size = UDim2.fromScale(1, 0),

			paint.text(),
			paint.no_background(),

			ui.flexitem("Shrink"),
		}
	end)
end

local function get_decimal_places(n: number)
	local s = tostring(n)
	local i = s:find("%.")
	return if i then s:len() - i else 0
end

return {
	DEFAULTS = DEFAULTS,
	row = row,

	is_valid_data_type = is_valid_data_type,
	get_decimal_places = get_decimal_places,
	get_component_count = get_component_count,
	read_component_value = read_component_value,
	write_component_value = write_component_value,
	get_default = get_default,

	create_input_label = create_input_label,
	create_input_component = create_input_component,
	create_increment = create_increment,
	create_color_preview = create_color_preview,
	create_data_type_inputs = create_data_type_inputs,
}


local ui = require("./uikit")
local root = require("./rootkit")
local config = require("../config")
local color4 = require("./color4")

type source<T> = root.source<T>
type value<T> = source<T> | T

local colors = config.colors
local read = root.read

local paint = {}

function paint.text(active: value<boolean>?)
    active = active or true
    return {
        Font = config.font,
        TextSize = config.textSize,
        TextTruncate = Enum.TextTruncate.AtEnd,
		TextXAlignment = Enum.TextXAlignment.Left,

        ui.apply_color("Text", function()
            return
                if read(active) == true then colors.Text
                else colors.TextDisabled
        end),
    }
end

function paint.background(active: value<boolean>?, hovered: value<boolean>?)
    return ui.apply_color("Background", function()
        return
            if read(hovered) == true then colors.FrameBgHovered
            elseif read(active) == true then colors.FrameBgActive
            else colors.FrameBg
    end)
end

function paint.title(active: value<boolean>?, collapsed: value<boolean>?)
    active = active or true
    return ui.apply_color("Background", function()
        return
            if read(collapsed) == true then colors.TitleBgCollapsed
            elseif read(active) == true then colors.TitleBg
            else colors.TitleBgDisabled
    end)
end

function paint.header(clicked: value<boolean>?, hovered: value<boolean>?)
    return ui.apply_color("Background", function()
        return
            if read(clicked) == true then colors.HeaderActive
            elseif read(hovered) == true then colors.HeaderHovered
            else colors.Header
    end)
end

function paint.button(active: value<boolean>?, hovered: value<boolean>?)
    return ui.apply_color("Background", function()
        return
            if read(active) == true then colors.ButtonActive
            elseif read(hovered) == true then colors.ButtonHovered
            else colors.Button
    end)
end

function paint.window(collapsed: value<boolean>?)
    return {
        ui.apply_color("Background", function()
            return
                if read(collapsed) == true then colors.WindowBgCollapsed
                else colors.WindowBg
        end),

        root.create("UIStroke") {
            Thickness = config.strokeThickness,

            Color = color4.read_color3(colors.BorderDark),
            Transparency = color4.read_alpha(colors.BorderDark),
        },
    }
end

function paint.tooltip()
    return {
        ui.apply_color("Background", colors.Tooltip),
        root.create("UIStroke") {
            ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
            Thickness = config.strokeThickness,

            Color = color4.read_color3(colors.Border),
            Transparency = color4.read_alpha(colors.Border),
        },
    }
end

function paint.menubar()
    return ui.apply_color("Background", colors.Menubar)
end

function paint.menu(selected: value<boolean>?, hovered: value<boolean>?)
    return ui.apply_color("Background", function()
        return
            if read(selected) == true then colors.MenuSelected
            elseif read(hovered) == true then colors.MenuHovered
            else colors.Menu
    end)
end

function paint.menuitem(active: value<boolean>?, hovered: value<boolean>?)
    return ui.apply_color("Background", function()
        return
            if read(active) == true then colors.MenuItemActive
            elseif read(hovered) == true then colors.MenuItemHovered
            else colors.MenuItem
    end)
end

function paint.no_background()
    return {
        BackgroundTransparency = 1,
    }
end

function paint.image()
    return {
        ScaleType = Enum.ScaleType.Fit,
        ui.apply_color("Image", colors.Image)
    }
end

function paint.seperator()
    return ui.apply_color("Background", colors.Seperator)
end

function paint.resize_grip()
    return ui.apply_color("Image", colors.ResizeGrip)
end

function paint.radialcenter()
    return ui.apply_color("Background", colors.RadialCenter)
end

function paint.option(selected: value<boolean>?, active: value<boolean>?, hovered: value<boolean>?)
    return ui.apply_color("Background", function()
        return
            if read(active) == true then colors.FrameBgActive
            elseif read(hovered) == true then colors.FrameBgHovered
            elseif read(selected) == true then colors.FrameBg
            else color4.create(0, 0, 0, 0)
    end)
end

function paint.dropdown(hovered: value<boolean>?)
    return ui.apply_color("Background", function()
        return
            if read(hovered) == true then colors.DropdownHovered
            else colors.Dropdown
    end)
end

return paint