local Types = require(script.Parent.Types)

return function(Iris: Types.Iris): Types.Internal
    --[=[
        @class Internal
        An internal class within Iris containing all the backend data and functions for Iris to operate.
        It is recommended that you don't generally interact with Internal unless you understand what you are doing.
    ]=]
    local Internal = {} :: Types.Internal

    --[[
        ---------------------------------
            [SECTION] Properties
        ---------------------------------
    ]]

    Internal._version = [[ 2.5.1 ]]

    Internal._started = false -- has Iris.connect been called yet
    Internal._shutdown = false
    Internal._cycleTick = 0 -- increments for each call to Cycle, used to determine the relative age and freshness of generated widgets
    Internal._deltaTime = 0

    -- Refresh
    Internal._globalRefreshRequested = false -- refresh means that all GUI is destroyed and regenerated, usually because a style change was made and needed to be propogated to all UI
    Internal._refreshCounter = 0 -- if true, when _Insert is called, the widget called will be regenerated
    Internal._refreshLevel = 1
    Internal._refreshStack = table.create(16)

    -- Widgets & Instances
    Internal._widgets = {}
    Internal._stackIndex = 1 -- Points to the index that IDStack is currently in, when computing cycle
    Internal._rootInstance = nil
    Internal._rootWidget = {
        ID = "R",
        type = "Root",
        Instance = Internal._rootInstance,
        ZIndex = 0,
        ZOffset = 0,
    }
    Internal._lastWidget = Internal._rootWidget -- widget which was most recently rendered

    -- Config
    Internal._rootConfig = {} -- root style which all widgets derive from
    Internal._config = Internal._rootConfig

    -- ID
    Internal._IDStack = { "R" }
    Internal._usedIDs = {} -- hash of IDs which are already used in a cycle, value is the # of occurances so that getID can assign a unique ID for each occurance
    Internal._pushedIds = {}
    Internal._newID = false
    Internal._nextWidgetId = nil

    -- State
    Internal._states = {} -- Iris.States

    -- Callback
    Internal._postCycleCallbacks = {}
    Internal._connectedFunctions = {} -- functions which run each Iris cycle, connected by the user
    Internal._connections = {}
    Internal._initFunctions = {}

    -- Error
    Internal._fullErrorTracebacks = game:GetService("RunService"):IsStudio()

    --[=[
        @within Internal
        @prop _cycleCoroutine thread

        The thread which handles all connected functions. Each connection is within a pcall statement which prevents
        Iris from crashing and instead stopping at the error.
    ]=]
    Internal._cycleCoroutine = coroutine.create(function()
        while Internal._started do
            for _, callback in Internal._connectedFunctions do
                debug.profilebegin("Iris/Connection")
                local status, _error: string = pcall(callback)
                debug.profileend()
                if not status then
                    -- any error reserts the _stackIndex for the next frame and yields the error.
                    Internal._stackIndex = 1
                    coroutine.yield(false, _error)
                end
            end
            -- after all callbacks, we yield so it only runs once a frame.
            coroutine.yield(true)
        end
    end)

    --[[
        -----------------------
            [SECTION] State
        -----------------------
    ]]

    --[=[
        @class State
        This class wraps a value in getters and setters, its main purpose is to allow primatives to be passed as objects.
        Constructors for this class are available in [Iris]

        ```lua
        local state = Iris.State(0) -- we initialise the state with a value of 0

        -- these are equivalent. Ideally you should use `:get()` and ignore `.value`.
        print(state:get())
        print(state.value)

        state:set(state:get() + 1) -- increments the state by getting the current value and adding 1.

        state:onChange(function(newValue)
            print(`The value of the state is now: {newValue}`)
        end)
        ```

        :::caution Caution: Callbacks
        Never call `:set()` on a state when inside the `:onChange()` callback of the same state. This will cause a continous callback.

        Never chain states together so that each state changes the value of another state in a cyclic nature. This will cause a continous callback.
        :::
    ]=]
    local StateClass = {}
    StateClass.__index = StateClass

    --[=[
        @within State
        @method get<T>
        @return T

        Returns the states current value.
    ]=]
    function StateClass:get<T>() -- you can also simply use .value
        return self.value
    end

    --[=[
        @within State
        @method set<T>
        @param newValue T
        @param force boolean? -- force an update to all connections
        @return T

        Allows the caller to assign the state object a new value, and returns the new value.
    ]=]
    function StateClass:set<T>(newValue: T, force: true?)
        if newValue == self.value and force ~= true then
            -- no need to update on no change.
            return self.value
        end
        self.value = newValue
        self.lastChangeTick = Iris.Internal._cycleTick
        for _, thisWidget: Types.Widget in self.ConnectedWidgets do
            if thisWidget.lastCycleTick ~= -1 then
                Internal._widgets[thisWidget.type].UpdateState(thisWidget)
            end
        end

        for _, callback in self.ConnectedFunctions do
            callback(newValue)
        end
        return self.value
    end

    --[=[
        @within State
        @method onChange<T>
        @param callback (newValue: T) -> ()
        @return () -> ()

        Allows the caller to connect a callback which is called when the states value is changed.

        :::caution Caution: Single
        Calling `:onChange()` every frame will add a new function every frame.
        You must ensure you are only calling `:onChange()` once for each callback for the state's entire lifetime.
        :::
    ]=]
    function StateClass:onChange<T>(callback: (newValue: T) -> ())
        local connectionIndex: number = #self.ConnectedFunctions + 1
        self.ConnectedFunctions[connectionIndex] = callback
        return function()
            self.ConnectedFunctions[connectionIndex] = nil
        end
    end

    --[=[
        @within State
        @method changed<T>
        @return boolean

        Returns true if the state was changed on this frame.
    ]=]
    function StateClass:changed<T>()
        return self.lastChangeTick + 1 == Internal._cycleTick
    end

    Internal.StateClass = StateClass

    --[[
        ---------------------------
            [SECTION] Functions
        ---------------------------
    ]]

    --[=[
        @within Internal
        @function _cycle

        Called every frame to handle all of the widget management. Any previous frame data is ammended and everything updates.
    ]=]
    function Internal._cycle(deltaTime: number)
        -- debug.profilebegin("Iris/Cycle")
        if Iris.Disabled then
            return -- Stops all rendering, effectively freezes the current frame with no interaction.
        end

        Internal._rootWidget.lastCycleTick = Internal._cycleTick
        if Internal._rootInstance == nil or Internal._rootInstance.Parent == nil then
            Iris.ForceRefresh()
        end

        for _, widget in Internal._lastVDOM do
            if widget.lastCycleTick ~= Internal._cycleTick and (widget.lastCycleTick ~= -1) then
                -- a widget which used to be rendered was not called last frame, so we discard it.
                -- if the cycle tick is -1 we have already discarded it.
                Internal._DiscardWidget(widget)
            end
        end

        -- represents all widgets created last frame. We keep the _lastVDOM to reuse widgets from the previous frame
        -- rather than creating a new instance every frame.
        setmetatable(Internal._lastVDOM, { __mode = "kv" })
        Internal._lastVDOM = Internal._VDOM
        Internal._VDOM = Internal._generateEmptyVDOM()

        -- anything that wnats to run before the frame.
        task.spawn(function()
            -- debug.profilebegin("Iris/PostCycleCallbacks")
            for _, callback in Internal._postCycleCallbacks do
                callback()
            end
            -- debug.profileend()
        end)

        if Internal._globalRefreshRequested then
            -- rerender every widget
            --debug.profilebegin("Iris Refresh")
            Internal._generateSelectionImageObject()
            Internal._globalRefreshRequested = false
            for _, widget in Internal._lastVDOM do
                Internal._DiscardWidget(widget)
            end
            Internal._generateRootInstance()
            Internal._lastVDOM = Internal._generateEmptyVDOM()
            --debug.profileend()
        end

        -- update counters
        Internal._cycleTick += 1
        Internal._deltaTime = deltaTime
        table.clear(Internal._usedIDs)

        -- if Internal.parentInstance:IsA("GuiBase2d") and math.min(Internal.parentInstance.AbsoluteSize.X, Internal.parentInstance.AbsoluteSize.Y) < 100 then
        --     error("Iris Parent Instance is too small")
        -- end
        local compatibleParent = (Internal.parentInstance:IsA("GuiBase2d") or Internal.parentInstance:IsA("BasePlayerGui"))
        if compatibleParent == false then
            error("The Iris parent instance will not display any GUIs.")
        end

        -- if we are running in Studio, we want full error tracebacks, so we don't have
        -- any pcall to protect from an error.
        if Internal._fullErrorTracebacks then
            -- debug.profilebegin("Iris/Cycle/Callback")
            for _, callback in Internal._connectedFunctions do
                callback()
            end
        else
            -- debug.profilebegin("Iris/Cycle/Coroutine")

            -- each frame we check on our thread status.
            local coroutineStatus = coroutine.status(Internal._cycleCoroutine)
            if coroutineStatus == "suspended" then
                -- suspended means it yielded, either because it was a complete success
                -- or it caught an error in the code. We run it again for this frame.
                local _, success, result = coroutine.resume(Internal._cycleCoroutine)
                if success == false then
                    -- Connected function code errored
                    error(result, 0)
                end
            elseif coroutineStatus == "running" then
                -- still running (probably because of an asynchronous method inside a connection).
                error("Iris cycleCoroutine took to long to yield. Connected functions should not yield.")
            else
                -- should never reach this (nothing you can do).
                error("unrecoverable state")
            end
            -- debug.profileend()
        end

        if Internal._stackIndex ~= 1 then
            -- has to be larger than 1 because of the check that it isnt below 1 in Iris.End
            Internal._stackIndex = 1
            error("Too few calls to Iris.End().", 0)
        end

        -- Errors if the end user forgot to pop all their ids as they would leak over into the next frame
        -- could also just clear, but that might be confusing behaviour.
        if #Internal._pushedIds ~= 0 then
            error("Too few calls to Iris.PopId().", 0)
        end

        -- debug.profileend()
    end

    --[=[
        @within Internal
        @ignore
        @function _NoOp

        A dummy function which does nothing. Used as a placeholder for optional methods in a widget class.
        Used in `Internal.WidgetConstructor`
    ]=]
    function Internal._NoOp() end

    --  Widget

    --[=[
        @within Internal
        @function WidgetConstructor
        @param type string -- name used to denote the widget class.
        @param widgetClass Types.WidgetClass -- table of methods for the new widget.

        For each widget, a widget class is created which handles all the operations of a widget. This removes the class nature
        of widgets, and simplifies the available functions which can be applied to any widget. The widgets themselves are
        dumb tables containing all the data but no methods to handle any of the data apart from events.
    ]=]
    function Internal.WidgetConstructor(type: string, widgetClass: Types.WidgetClass)
        local Fields = {
            All = {
                Required = {
                    "Generate", -- generates the instance.
                    "Discard",
                    "Update",

                    -- not methods !
                    "Args",
                    "Events",
                    "hasChildren",
                    "hasState",
                },
                Optional = {},
            },
            IfState = {
                Required = {
                    "GenerateState",
                    "UpdateState",
                },
                Optional = {},
            },
            IfChildren = {
                Required = {
                    "ChildAdded", -- returns the parent of the child widget.
                },
                Optional = {
                    "ChildDiscarded",
                },
            },
        }

        -- we ensure all essential functions and properties are present, otherwise the code will break later.
        -- some functions will only be needed if the widget has children or has state.
        local thisWidget = {} :: Types.WidgetClass
        for _, field in Fields.All.Required do
            assert(widgetClass[field] ~= nil, `field {field} is missing from widget {type}, it is required for all widgets`)
            thisWidget[field] = widgetClass[field]
        end

        for _, field in Fields.All.Optional do
            if widgetClass[field] == nil then
                -- assign a dummy function which does nothing.
                thisWidget[field] = Internal._NoOp
            else
                thisWidget[field] = widgetClass[field]
            end
        end

        if widgetClass.hasState then
            for _, field in Fields.IfState.Required do
                assert(widgetClass[field] ~= nil, `field {field} is missing from widget {type}, it is required for all widgets with state`)
                thisWidget[field] = widgetClass[field]
            end
            for _, field in Fields.IfState.Optional do
                if widgetClass[field] == nil then
                    thisWidget[field] = Internal._NoOp
                else
                    thisWidget[field] = widgetClass[field]
                end
            end
        end

        if widgetClass.hasChildren then
            for _, field in Fields.IfChildren.Required do
                assert(widgetClass[field] ~= nil, `field {field} is missing from widget {type}, it is required for all widgets with children`)
                thisWidget[field] = widgetClass[field]
            end
            for _, field in Fields.IfChildren.Optional do
                if widgetClass[field] == nil then
                    thisWidget[field] = Internal._NoOp
                else
                    thisWidget[field] = widgetClass[field]
                end
            end
        end

        -- an internal table of all widgets to the widget class.
        Internal._widgets[type] = thisWidget
        -- allowing access to the index for each widget argument.
        Iris.Args[type] = thisWidget.Args

        local ArgNames = {}
        for index, argument in thisWidget.Args do
            ArgNames[argument] = index
        end
        thisWidget.ArgNames = ArgNames

        for index, _ in thisWidget.Events do
            if Iris.Events[index] == nil then
                Iris.Events[index] = function()
                    return Internal._EventCall(Internal._lastWidget, index)
                end
            end
        end
    end

    --[=[
        @within Internal
        @function _Insert
        @param widgetType: string -- name of widget class.
        @param arguments { [string]: number } -- arguments of the widget.
        @param states { [string]: States<any> }? -- states of the widget.
        @return Widget -- the widget.

        Every widget is created through _Insert. An ID is generated based on the line of the calling code and is used to
        find the previous frame widget if it exists. If no widget exists, a new one is created.
    ]=]
    function Internal._Insert(widgetType: string, args: Types.WidgetArguments?, states: Types.WidgetStates?)
        local ID = Internal._getID(3)
        --debug.profilebegin(ID)

        -- fetch the widget class which contains all the functions for the widget.
        local thisWidgetClass = Internal._widgets[widgetType]

        if Internal._VDOM[ID] then
            -- widget already created once this frame, so we can append to it.
            return Internal._ContinueWidget(ID, widgetType)
        end

        local arguments = {} :: Types.Arguments
        if args ~= nil then
            if type(args) ~= "table" then
                args = { args }
            end

            -- convert the arguments to a key-value dictionary so arguments can be referred to by their name and not index.
            for index, argument in args do
                assert(index > 0, `Widget Arguments must be a positive number, not {index} of type {typeof(index)} for {argument}.`)
                arguments[thisWidgetClass.ArgNames[index]] = argument
            end
        end
        -- prevents tampering with the arguments which are used to check for changes.
        table.freeze(arguments)

        local lastWidget = Internal._lastVDOM[ID]
        if lastWidget and widgetType == lastWidget.type then
            -- found a matching widget from last frame.
            if Internal._refreshCounter > 0 then
                -- we are redrawing every widget.
                Internal._DiscardWidget(lastWidget)
                lastWidget = nil
            end
        end
        local thisWidget = if lastWidget == nil then Internal._GenNewWidget(widgetType, arguments, states, ID) else lastWidget

        local parentWidget = thisWidget.parentWidget

        if thisWidget.type ~= "Window" and thisWidget.type ~= "Tooltip" then
            if thisWidget.ZIndex ~= parentWidget.ZOffset then
                parentWidget.ZUpdate = true
            end

            if parentWidget.ZUpdate then
                thisWidget.ZIndex = parentWidget.ZOffset
                if thisWidget.Instance then
                    thisWidget.Instance.ZIndex = thisWidget.ZIndex
                    thisWidget.Instance.LayoutOrder = thisWidget.ZIndex
                end
            end
        end

        -- since rows are not instances, but will be removed if not updated, we have to add specific table code.
        if parentWidget.type == "Table" then
            local Table = parentWidget :: Types.Table
            Table._rowCycles[Table._rowIndex] = Internal._cycleTick
        end

        if Internal._deepCompare(thisWidget.providedArguments, arguments) == false then
            -- the widgets arguments have changed, the widget should update to reflect changes.
            -- providedArguments is the frozen table which will not change.
            -- the arguments can be altered internally, which happens for the input widgets.
            thisWidget.arguments = Internal._deepCopy(arguments)
            thisWidget.providedArguments = arguments
            thisWidgetClass.Update(thisWidget)
        end

        thisWidget.lastCycleTick = Internal._cycleTick
        parentWidget.ZOffset += 1

        if thisWidgetClass.hasChildren then
            local thisParent = thisWidget :: Types.ParentWidget
            -- a parent widget, so we increase our depth.
            thisParent.ZOffset = 0
            thisParent.ZUpdate = false
            Internal._stackIndex += 1
            Internal._IDStack[Internal._stackIndex] = thisWidget.ID
        end

        Internal._VDOM[ID] = thisWidget
        Internal._lastWidget = thisWidget

        --debug.profileend()

        return thisWidget
    end

    --[=[
        @within Internal
        @function _GenNewWidget
        @param widgetType string
        @param arguments { [string]: any } -- arguments of the widget.
        @param states { [string]: State<any> }? -- states of the widget.
        @param ID ID -- id of the new widget. Determined in `Internal._Insert`
        @return Widget -- the newly created widget.

        All widgets are created as tables with properties. The widget class contains the functions to create the UI instances and
        update the widget or change state.
    ]=]
    function Internal._GenNewWidget(widgetType: string, arguments: Types.Arguments, states: Types.WidgetStates?, ID: Types.ID)
        local parentId = Internal._IDStack[Internal._stackIndex]
        local parentWidget: Types.ParentWidget = Internal._VDOM[parentId]
        local thisWidgetClass = Internal._widgets[widgetType]

        -- widgets are just tables with properties.
        local thisWidget = {} :: Types.Widget
        setmetatable(thisWidget, thisWidget)

        thisWidget.ID = ID
        thisWidget.type = widgetType
        thisWidget.parentWidget = parentWidget
        thisWidget.trackedEvents = {}
        -- thisWidget.UID = HttpService:GenerateGUID(false):sub(0, 8)

        -- widgets have lots of space to ensure they are always visible.
        thisWidget.ZIndex = parentWidget.ZOffset

        thisWidget.Instance = thisWidgetClass.Generate(thisWidget)
        -- tooltips set their parent in the generation method, so we need to udpate it here
        parentWidget = thisWidget.parentWidget

        if Internal._config.Parent then
            thisWidget.Instance.Parent = Internal._config.Parent
        else
            thisWidget.Instance.Parent = Internal._widgets[parentWidget.type].ChildAdded(parentWidget, thisWidget)
        end

        -- we can modify the arguments table, but keep a frozen copy to compare for user-end changes.
        thisWidget.providedArguments = arguments
        thisWidget.arguments = Internal._deepCopy(arguments)
        thisWidgetClass.Update(thisWidget)

        local eventMTParent
        if thisWidgetClass.hasState then
            local stateWidget = thisWidget :: Types.StateWidget
            if states then
                for index, state in states do
                    if not (type(state) == "table" and getmetatable(state :: any) == Internal.StateClass) then
                        -- generate a new state.
                        states[index] = Internal._widgetState(stateWidget, index, state)
                    end
                    states[index].lastChangeTick = Internal._cycleTick
                end

                stateWidget.state = states
                for _, state in states do
                    state.ConnectedWidgets[stateWidget.ID] = stateWidget
                end
            else
                stateWidget.state = {}
            end

            thisWidgetClass.GenerateState(stateWidget)
            thisWidgetClass.UpdateState(stateWidget)

            -- the state MT can't be itself because state has to explicitly only contain stateClass objects
            stateWidget.stateMT = {}
            setmetatable(stateWidget.state, stateWidget.stateMT)

            stateWidget.__index = stateWidget.state
            eventMTParent = stateWidget.stateMT
        else
            eventMTParent = thisWidget
        end

        eventMTParent.__index = function(_, eventName)
            return function()
                return Internal._EventCall(thisWidget, eventName)
            end
        end
        return thisWidget
    end

    --[=[
        @within Internal
        @function _ContinueWidget
        @param ID ID -- id of the widget.
        @param widgetType string
        @return Widget -- the widget.

        Since the widget has already been created this frame, we can just add it back to the stack. There is no checking of
        arguments or states.
        Basically equivalent to the end of `Internal._Insert`.
    ]=]
    function Internal._ContinueWidget(ID: Types.ID, widgetType: string)
        local thisWidgetClass = Internal._widgets[widgetType]
        local thisWidget = Internal._VDOM[ID]

        if thisWidgetClass.hasChildren then
            -- a parent widget so we increase our depth.
            Internal._stackIndex += 1
            Internal._IDStack[Internal._stackIndex] = thisWidget.ID
        end

        Internal._lastWidget = thisWidget
        return thisWidget
    end

    --[=[
        @within Internal
        @function _DiscardWidget
        @param widgetToDiscard Widget

        Destroys the widget instance and updates any parent. This happens if the widget was not called in the
        previous frame. There is no code which needs to update any widget tables since they are already reset
        at the start before discarding happens.
    ]=]
    function Internal._DiscardWidget(widgetToDiscard: Types.Widget)
        local widgetParent = widgetToDiscard.parentWidget
        if widgetParent then
            -- if the parent needs to update it's children.
            Internal._widgets[widgetParent.type].ChildDiscarded(widgetParent, widgetToDiscard)
        end

        -- using the widget class discard function.
        Internal._widgets[widgetToDiscard.type].Discard(widgetToDiscard)

        -- mark as discarded
        widgetToDiscard.lastCycleTick = -1
    end

    --[=[
        @within Internal
        @function _widgetState
        @param thisWidget Widget -- widget the state belongs to.
        @param stateName string
        @param initialValue any
        @return State<any> -- the state for the widget.

        Connects the state to the widget. If no state exists then a new one is created. Called for every state in every
        widget if the user does not provide a state.
    ]=]
    function Internal._widgetState<T>(thisWidget: Types.StateWidget, stateName: string, initialValue: T)
        local ID = thisWidget.ID .. stateName
        if Internal._states[ID] then
            Internal._states[ID].ConnectedWidgets[thisWidget.ID] = thisWidget
            Internal._states[ID].lastChangeTick = Internal._cycleTick
            return Internal._states[ID]
        else
            local newState = {
                ID = ID,
                value = initialValue,
                lastChangeTick = Internal._cycleTick,
                ConnectedWidgets = { [thisWidget.ID] = thisWidget },
                ConnectedFunctions = {},
            } :: Types.State<T>
            setmetatable(newState, Internal.StateClass)
            Internal._states[ID] = newState
            return newState
        end
    end

    --[=[
        @within Internal
        @function _EventCall
        @param thisWidget Widget
        @param evetName string
        @return boolean -- the value of the event.

        A wrapper for any event on any widget. Automatically, Iris does not initialize events unless they are explicitly
        called so in the first frame, the event connections are set up. Every event is a function which returns a boolean.
    ]=]
    function Internal._EventCall(thisWidget: Types.Widget, eventName: string)
        local Events = Internal._widgets[thisWidget.type].Events
        local Event = Events[eventName]
        assert(Event ~= nil, `widget {thisWidget.type} has no event of name {eventName}`)

        if thisWidget.trackedEvents[eventName] == nil then
            Event.Init(thisWidget)
            thisWidget.trackedEvents[eventName] = true
        end
        return Event.Get(thisWidget)
    end

    --[=[
        @within Internal
        @function _GetParentWidget
        @return Widget -- the parent widget

        Returns the parent widget of the currently active widget, based on the stack depth.
    ]=]
    function Internal._GetParentWidget(): Types.ParentWidget
        return Internal._VDOM[Internal._IDStack[Internal._stackIndex]]
    end

    -- Generate

    --[=[
        @ignore
        @within Internal
        @function _generateEmptyVDOM
        @return { [ID]: Widget }

        Creates the VDOM at the start of each frame containing just the root instance.
    ]=]
    function Internal._generateEmptyVDOM()
        return {
            ["R"] = Internal._rootWidget,
        }
    end

    --[=[
        @ignore
        @within Internal
        @function _generateRootInstance

        Creates the root instance.
    ]=]
    function Internal._generateRootInstance()
        -- unsafe to call before Internal.connect
        Internal._rootInstance = Internal._widgets["Root"].Generate(Internal._widgets["Root"])
        Internal._rootInstance.Parent = Internal.parentInstance
        Internal._rootWidget.Instance = Internal._rootInstance
    end

    --[=[
        @ignore
        @within Internal
        @function _generateSelctionImageObject

        Creates the selection object for buttons.
    ]=]
    function Internal._generateSelectionImageObject()
        if Internal.SelectionImageObject then
            Internal.SelectionImageObject:Destroy()
        end

        local SelectionImageObject = Instance.new("Frame")
        SelectionImageObject.Position = UDim2.fromOffset(-1, -1)
        SelectionImageObject.Size = UDim2.new(1, 2, 1, 2)
        SelectionImageObject.BackgroundColor3 = Internal._config.SelectionImageObjectColor
        SelectionImageObject.BackgroundTransparency = Internal._config.SelectionImageObjectTransparency
        SelectionImageObject.BorderSizePixel = 0

        Internal._utility.UIStroke(SelectionImageObject, 1, Internal._config.SelectionImageObjectBorderColor, Internal._config.SelectionImageObjectBorderTransparency)
        Internal._utility.UICorner(SelectionImageObject, 2)

        Internal.SelectionImageObject = SelectionImageObject
    end

    -- Utility

    --[=[
        @within Internal
        @function _getID
        @param levelsToIgnore number -- used to skip over internal calls to `_getID`.
        @return ID

        Generates a unique ID for each widget which is based on the line that the widget is
        created from. This ensures that the function is heuristic and always returns the same
        id for the same widget.
    ]=]
    function Internal._getID(levelsToIgnore: number)
        if Internal._nextWidgetId then
            local ID = Internal._nextWidgetId
            Internal._nextWidgetId = nil
            return ID
        end

        local i = 1 + (levelsToIgnore or 1)
        local ID = ""
        local levelInfo = debug.info(i, "l")
        while levelInfo ~= -1 and levelInfo ~= nil do
            ID ..= "+" .. levelInfo
            i += 1
            levelInfo = debug.info(i, "l")
        end

        local discriminator = Internal._usedIDs[ID]
        if discriminator then
            Internal._usedIDs[ID] += 1
            discriminator += 1
        else
            Internal._usedIDs[ID] = 1
            discriminator = 1
        end

        if #Internal._pushedIds == 0 then
            return ID .. ":" .. discriminator
        elseif Internal._newID then
            Internal._newID = false
            return ID .. "::" .. table.concat(Internal._pushedIds, "\\")
        else
            return ID .. ":" .. discriminator .. ":" .. table.concat(Internal._pushedIds, "\\")
        end
    end

    --[=[
        @ignore
        @within Internal
        @function _deepCompare
        @param t1 {}
        @param t2 {}
        @return boolean

        Compares two tables to check if they are the same. It uses a recursive iteration through one table
        to compare against the other. Used to determine if the arguments of a widget have changed since last
        frame.
    ]=]
    function Internal._deepCompare(t1: {}, t2: {})
        -- unoptimized ?
        for i, v1 in t1 do
            local v2 = t2[i]
            if type(v1) == "table" then
                if v2 and type(v2) == "table" then
                    if Internal._deepCompare(v1, v2) == false then
                        return false
                    end
                else
                    return false
                end
            else
                if type(v1) ~= type(v2) or v1 ~= v2 then
                    return false
                end
            end
        end

        return true
    end

    --[=[
        @ignore
        @within Internal
        @function _deepCopy
        @param t {}
        @return {}

        Performs a deep copy of a table so that neither table contains a shared reference.
    ]=]
    function Internal._deepCopy(t: {}): {}
        local copy: {} = table.clone(t)

        for k, v in t do
            if type(v) == "table" then
                copy[k] = Internal._deepCopy(v)
            end
        end

        return copy
    end

    -- VDOM
    Internal._lastVDOM = Internal._generateEmptyVDOM()
    Internal._VDOM = Internal._generateEmptyVDOM()

    Iris.Internal = Internal
    Iris._config = Internal._config
    return Internal
end


local Types = require(script.Parent.Types)

export type ID = Types.ID
export type State<T> = Types.State<T>

export type Widget = Types.Widget
export type Root = Types.Root
export type Window = Types.Window
export type Tooltip = Types.Tooltip
export type MenuBar = Types.MenuBar
export type Menu = Types.Menu
export type MenuItem = Types.MenuItem
export type MenuToggle = Types.MenuToggle
export type Separator = Types.Separator
export type Indent = Types.Indent
export type SameLine = Types.SameLine
export type Group = Types.Group
export type Text = Types.Text
export type SeparatorText = Types.SeparatorText
export type Button = Types.Button
export type Checkbox = Types.Checkbox
export type RadioButton = Types.RadioButton
export type Image = Types.Image
export type ImageButton = Types.ImageButton
export type Tree = Types.Tree
export type CollapsingHeader = Types.CollapsingHeader
export type TabBar = Types.TabBar
export type Tab = Types.Tab
export type Input<T> = Types.Input<T>
export type InputColor3 = Types.InputColor3
export type InputColor4 = Types.InputColor4
export type InputEnum = Types.InputEnum
export type InputText = Types.InputText
export type Selectable = Types.Selectable
export type Combo = Types.Combo
export type ProgressBar = Types.ProgressBar
export type PlotLines = Types.PlotLines
export type PlotHistogram = Types.PlotHistogram
export type Table = Types.Table

export type Iris = Types.Iris

return {}


local WidgetTypes = require(script.Parent.WidgetTypes)

export type ID = WidgetTypes.ID
export type State<T> = WidgetTypes.State<T>

export type Hovered = WidgetTypes.Hovered
export type Clicked = WidgetTypes.Clicked
export type RightClicked = WidgetTypes.RightClicked
export type DoubleClicked = WidgetTypes.DoubleClicked
export type CtrlClicked = WidgetTypes.CtrlClicked
export type Active = WidgetTypes.Active
export type Checked = WidgetTypes.Checked
export type Unchecked = WidgetTypes.Unchecked
export type Opened = WidgetTypes.Opened
export type Closed = WidgetTypes.Closed
export type Collapsed = WidgetTypes.Collapsed
export type Uncollapsed = WidgetTypes.Uncollapsed
export type Selected = WidgetTypes.Selected
export type Unselected = WidgetTypes.Unselected
export type Changed = WidgetTypes.Changed
export type NumberChanged = WidgetTypes.NumberChanged
export type TextChanged = WidgetTypes.TextChanged

export type Widget = WidgetTypes.Widget
export type ParentWidget = WidgetTypes.ParentWidget
export type StateWidget = WidgetTypes.StateWidget

export type Root = WidgetTypes.Root
export type Window = WidgetTypes.Window
export type Tooltip = WidgetTypes.Tooltip
export type MenuBar = WidgetTypes.MenuBar
export type Menu = WidgetTypes.Menu
export type MenuItem = WidgetTypes.MenuItem
export type MenuToggle = WidgetTypes.MenuToggle
export type Separator = WidgetTypes.Separator
export type Indent = WidgetTypes.Indent
export type SameLine = WidgetTypes.SameLine
export type Group = WidgetTypes.Group
export type Text = WidgetTypes.Text
export type SeparatorText = WidgetTypes.SeparatorText
export type Button = WidgetTypes.Button
export type Checkbox = WidgetTypes.Checkbox
export type RadioButton = WidgetTypes.RadioButton
export type Image = WidgetTypes.Image
export type ImageButton = WidgetTypes.ImageButton
export type Tree = WidgetTypes.Tree
export type CollapsingHeader = WidgetTypes.CollapsingHeader
export type TabBar = WidgetTypes.TabBar
export type Tab = WidgetTypes.Tab
export type Input<T> = WidgetTypes.Input<T>
export type InputColor3 = WidgetTypes.InputColor3
export type InputColor4 = WidgetTypes.InputColor4
export type InputEnum = WidgetTypes.InputEnum
export type InputText = WidgetTypes.InputText
export type Selectable = WidgetTypes.Selectable
export type Combo = WidgetTypes.Combo
export type ProgressBar = WidgetTypes.ProgressBar
export type PlotLines = WidgetTypes.PlotLines
export type PlotHistogram = WidgetTypes.PlotHistogram
export type Table = WidgetTypes.Table

export type InputDataType = number | Vector2 | Vector3 | UDim | UDim2 | Color3 | Rect | { number }

export type Argument = any
export type Arguments = {
    [string]: Argument,
    Text: string,
    TextHint: string,
    TextOverlay: string,
    ReadOnly: boolean,
    MultiLine: boolean,
    Wrapped: boolean,
    Color: Color3,
    RichText: boolean,

    Increment: InputDataType,
    Min: InputDataType,
    Max: InputDataType,
    Format: { string },
    UseFloats: boolean,
    UseHSV: boolean,
    UseHex: boolean,
    Prefix: { string },
    BaseLine: number,

    Width: number,
    Height: number,
    VerticalAlignment: Enum.VerticalAlignment,
    HorizontalAlignment: Enum.HorizontalAlignment,
    Index: any,
    Image: string,
    Size: UDim2,
    Rect: Rect,
    ScaleType: Enum.ScaleType,
    TileSize: UDim2,
    SliceCenter: Rect,
    SliceScale: number,
    ResampleMode: Enum.ResamplerMode,

    SpanAvailWidth: boolean,
    NoIdent: boolean,
    NoClick: boolean,
    NoButtons: boolean,
    NoButton: boolean,
    NoPreview: boolean,

    NumColumns: number,
    RowBg: boolean,
    BordersOuter: boolean,
    BordersInner: boolean,

    Title: string,
    NoTitleBar: boolean,
    NoBackground: boolean,
    NoCollapse: boolean,
    NoClose: boolean,
    NoMove: boolean,
    NoScrollbar: boolean,
    NoResize: boolean,
    NoMenu: boolean,

    KeyCode: Enum.KeyCode,
    ModifierKey: Enum.ModifierKey,
    Disabled: boolean,
}

export type States = {
    [string]: State<any>,
    number: State<number>,
    color: State<Color3>,
    transparency: State<number>,
    editingText: State<boolean>,
    index: State<any>,

    size: State<Vector2>,
    position: State<Vector2>,
    progress: State<number>,
    scrollDistance: State<number>,

    isChecked: State<boolean>,
    isOpened: State<boolean>,
    isUncollapsed: State<boolean>,
}

export type Event = {
    Init: (Widget) -> (),
    Get: (Widget) -> boolean,
}
export type Events = { [string]: Event }

-- Widgets

export type WidgetArguments = { [number]: Argument }
export type WidgetStates = {
    [string]: State<any>,
    number: State<number>?,
    color: State<Color3>?,
    transparency: State<number>?,
    editingText: State<boolean>?,
    index: State<any>?,

    size: State<Vector2>?,
    position: State<Vector2>?,
    progress: State<number>?,
    scrollDistance: State<number>?,
    values: State<number>?,

    isChecked: State<boolean>?,
    isOpened: State<boolean>?,
    isUncollapsed: State<boolean>?,
}

export type WidgetClass = {
    Generate: (thisWidget: Widget) -> GuiObject,
    Discard: (thisWidget: Widget) -> (),
    Update: (thisWidget: Widget, ...any) -> (),

    Args: { [string]: number },
    Events: Events,
    hasChildren: boolean,
    hasState: boolean,
    ArgNames: { [number]: string },

    GenerateState: (thisWidget: Widget) -> (),
    UpdateState: (thisWidget: Widget) -> (),

    ChildAdded: (thisWidget: Widget, thisChild: Widget) -> GuiObject,
    ChildDiscarded: (thisWidget: Widget, thisChild: Widget) -> (),
}

-- Iris

export type Internal = {
    --[[
        --------------
          PROPERTIES
        --------------
    ]]
    _version: string,
    _started: boolean,
    _shutdown: boolean,
    _cycleTick: number,
    _deltaTime: number,
    _eventConnection: RBXScriptConnection?,

    -- Refresh
    _globalRefreshRequested: boolean,
    _refreshCounter: number,
    _refreshLevel: number,
    _refreshStack: { boolean },

    -- Widgets & Instances
    _widgets: { [string]: WidgetClass },
    _widgetCount: number,
    _stackIndex: number,
    _rootInstance: GuiObject?,
    _rootWidget: ParentWidget,
    _lastWidget: Widget,
    SelectionImageObject: Frame,
    parentInstance: BasePlayerGui | GuiBase2d,
    _utility: WidgetUtility,

    -- Config
    _rootConfig: Config,
    _config: Config,

    -- ID
    _IDStack: { ID },
    _usedIDs: { [ID]: number },
    _newID: boolean,
    _pushedIds: { ID },
    _nextWidgetId: ID?,

    -- VDOM
    _lastVDOM: { [ID]: Widget },
    _VDOM: { [ID]: Widget },

    -- State
    _states: { [ID]: State<any> },

    -- Callback
    _postCycleCallbacks: { () -> () },
    _connectedFunctions: { () -> () },
    _connections: { RBXScriptConnection },
    _initFunctions: { () -> () },
    _cycleCoroutine: thread?,

    --[[
        ---------
          STATE
        ---------
    ]]

    StateClass: {
        __index: any,

        get: <T>(self: State<T>) -> any,
        set: <T>(self: State<T>, newValue: any) -> any,
        onChange: <T>(self: State<T>, callback: (newValue: any) -> ()) -> (),
    },

    --[[
        -------------
          FUNCTIONS
        -------------
    ]]
    _cycle: (deltaTime: number) -> (),
    _NoOp: () -> (),

    -- Widget
    WidgetConstructor: (type: string, widgetClass: WidgetClass) -> (),
    _Insert: (widgetType: string, arguments: WidgetArguments?, states: WidgetStates?) -> Widget,
    _GenNewWidget: (widgetType: string, arguments: Arguments, states: WidgetStates?, ID: ID) -> Widget,
    _ContinueWidget: (ID: ID, widgetType: string) -> Widget,
    _DiscardWidget: (widgetToDiscard: Widget) -> (),

    _widgetState: (thisWidget: Widget, stateName: string, initialValue: any) -> State<any>,
    _EventCall: (thisWidget: Widget, eventName: string) -> boolean,
    _GetParentWidget: () -> ParentWidget,
    SetFocusedWindow: (thisWidget: WidgetTypes.Window?) -> (),

    -- Generate
    _generateEmptyVDOM: () -> { [ID]: Widget },
    _generateRootInstance: () -> (),
    _generateSelectionImageObject: () -> (),

    -- Utility
    _getID: (levelsToIgnore: number) -> ID,
    _deepCompare: (t1: {}, t2: {}) -> boolean,
    _deepCopy: (t: {}) -> {},
}

export type WidgetUtility = {
    GuiService: GuiService,
    RunService: RunService,
    TextService: TextService,
    UserInputService: UserInputService,
    ContextActionService: ContextActionService,

    getTime: () -> number,
    getMouseLocation: () -> Vector2,

    ICONS: {
        BLANK_SQUARE: string,
        RIGHT_POINTING_TRIANGLE: string,
        DOWN_POINTING_TRIANGLE: string,
        MULTIPLICATION_SIGN: string,
        BOTTOM_RIGHT_CORNER: string,
        CHECKMARK: string,
        BORDER: string,
        ALPHA_BACKGROUND_TEXTURE: string,
        UNKNOWN_TEXTURE: string,
    },

    GuiOffset: Vector2,
    MouseOffset: Vector2,

    findBestWindowPosForPopup: (refPos: Vector2, size: Vector2, outerMin: Vector2, outerMax: Vector2) -> Vector2,
    getScreenSizeForWindow: (thisWidget: Widget) -> Vector2,
    isPosInsideRect: (pos: Vector2, rectMin: Vector2, rectMax: Vector2) -> boolean,
    extend: (superClass: WidgetClass, { [any]: any }) -> WidgetClass,
    discardState: (thisWidget: Widget) -> (),

    UIPadding: (Parent: GuiObject, PxPadding: Vector2) -> UIPadding,
    UIListLayout: (Parent: GuiObject, FillDirection: Enum.FillDirection, Padding: UDim) -> UIListLayout,
    UIStroke: (Parent: GuiObject, Thickness: number, Color: Color3, Transparency: number) -> UIStroke,
    UICorner: (Parent: GuiObject, PxRounding: number?) -> UICorner,
    UISizeConstraint: (Parent: GuiObject, MinSize: Vector2?, MaxSize: Vector2?) -> UISizeConstraint,

    applyTextStyle: (thisInstance: TextLabel | TextButton | TextBox) -> (),
    applyInteractionHighlights: (Property: string, Button: GuiButton, Highlightee: GuiObject, Colors: { [string]: any }) -> (),
    applyInteractionHighlightsWithMultiHighlightee: (Property: string, Button: GuiButton, Highlightees: { { GuiObject | { [string]: Color3 | number } } }) -> (),
    applyFrameStyle: (thisInstance: GuiObject, noPadding: boolean?, noCorner: boolean?) -> (),

    applyButtonClick: (thisInstance: GuiButton, callback: () -> ()) -> (),
    applyButtonDown: (thisInstance: GuiButton, callback: (x: number, y: number) -> ()) -> (),
    applyMouseEnter: (thisInstance: GuiObject, callback: (x: number, y: number) -> ()) -> (),
    applyMouseMoved: (thisInstance: GuiObject, callback: (x: number, y: number) -> ()) -> (),
    applyMouseLeave: (thisInstance: GuiObject, callback: (x: number, y: number) -> ()) -> (),
    applyInputBegan: (thisInstance: GuiObject, callback: (input: InputObject) -> ()) -> (),
    applyInputEnded: (thisInstance: GuiObject, callback: (input: InputObject) -> ()) -> (),

    registerEvent: (event: string, callback: (...any) -> ()) -> (),

    EVENTS: {
        hover: (pathToHovered: (thisWidget: Widget & Hovered) -> GuiObject) -> Event,
        click: (pathToClicked: (thisWidget: Widget & Clicked) -> GuiButton) -> Event,
        rightClick: (pathToClicked: (thisWidget: Widget & RightClicked) -> GuiButton) -> Event,
        doubleClick: (pathToClicked: (thisWidget: Widget & DoubleClicked) -> GuiButton) -> Event,
        ctrlClick: (pathToClicked: (thisWidget: Widget & CtrlClicked) -> GuiButton) -> Event,
    },

    abstractButton: WidgetClass,
}

export type Config = {
    TextColor: Color3,
    TextTransparency: number,
    TextDisabledColor: Color3,
    TextDisabledTransparency: number,

    BorderColor: Color3,
    BorderActiveColor: Color3,
    BorderTransparency: number,
    BorderActiveTransparency: number,

    WindowBgColor: Color3,
    WindowBgTransparency: number,
    ScrollbarGrabColor: Color3,
    ScrollbarGrabTransparency: number,
    PopupBgColor: Color3,
    PopupBgTransparency: number,

    TitleBgColor: Color3,
    TitleBgTransparency: number,
    TitleBgActiveColor: Color3,
    TitleBgActiveTransparency: number,
    TitleBgCollapsedColor: Color3,
    TitleBgCollapsedTransparency: number,

    MenubarBgColor: Color3,
    MenubarBgTransparency: number,

    FrameBgColor: Color3,
    FrameBgTransparency: number,
    FrameBgHoveredColor: Color3,
    FrameBgHoveredTransparency: number,
    FrameBgActiveColor: Color3,
    FrameBgActiveTransparency: number,

    ButtonColor: Color3,
    ButtonTransparency: number,
    ButtonHoveredColor: Color3,
    ButtonHoveredTransparency: number,
    ButtonActiveColor: Color3,
    ButtonActiveTransparency: number,

    ImageColor: Color3,
    ImageTransparency: number,

    SliderGrabColor: Color3,
    SliderGrabTransparency: number,
    SliderGrabActiveColor: Color3,
    SliderGrabActiveTransparency: number,

    HeaderColor: Color3,
    HeaderTransparency: number,
    HeaderHoveredColor: Color3,
    HeaderHoveredTransparency: number,
    HeaderActiveColor: Color3,
    HeaderActiveTransparency: number,

    TabColor: Color3,
    TabTransparency: number,
    TabHoveredColor: Color3,
    TabHoveredTransparency: number,
    TabActiveColor: Color3,
    TabActiveTransparency: number,

    SelectionImageObjectColor: Color3,
    SelectionImageObjectTransparency: number,
    SelectionImageObjectBorderColor: Color3,
    SelectionImageObjectBorderTransparency: number,

    TableBorderStrongColor: Color3,
    TableBorderStrongTransparency: number,
    TableBorderLightColor: Color3,
    TableBorderLightTransparency: number,
    TableRowBgColor: Color3,
    TableRowBgTransparency: number,
    TableRowBgAltColor: Color3,
    TableRowBgAltTransparency: number,
    TableHeaderColor: Color3,
    TableHeaderTransparency: number,

    NavWindowingHighlightColor: Color3,
    NavWindowingHighlightTransparency: number,
    NavWindowingDimBgColor: Color3,
    NavWindowingDimBgTransparency: number,

    SeparatorColor: Color3,
    SeparatorTransparency: number,

    CheckMarkColor: Color3,
    CheckMarkTransparency: number,

    PlotLinesColor: Color3,
    PlotLinesTransparency: number,
    PlotLinesHoveredColor: Color3,
    PlotLinesHoveredTransparency: number,
    PlotHistogramColor: Color3,
    PlotHistogramTransparency: number,
    PlotHistogramHoveredColor: Color3,
    PlotHistogramHoveredTransparency: number,

    ResizeGripColor: Color3,
    ResizeGripTransparency: number,
    ResizeGripHoveredColor: Color3,
    ResizeGripHoveredTransparency: number,
    ResizeGripActiveColor: Color3,
    ResizeGripActiveTransparency: number,

    HoverColor: Color3,
    HoverTransparency: number,

    -- Sizes
    ItemWidth: UDim,
    ContentWidth: UDim,
    ContentHeight: UDim,

    WindowPadding: Vector2,
    WindowResizePadding: Vector2,
    FramePadding: Vector2,
    ItemSpacing: Vector2,
    ItemInnerSpacing: Vector2,
    CellPadding: Vector2,
    DisplaySafeAreaPadding: Vector2,
    IndentSpacing: number,
    SeparatorTextPadding: Vector2,

    TextFont: Font,
    TextSize: number,
    FrameBorderSize: number,
    FrameRounding: number,
    GrabRounding: number,
    WindowBorderSize: number,
    WindowTitleAlign: Enum.LeftRight,
    PopupBorderSize: number,
    PopupRounding: number,
    ScrollbarSize: number,
    GrabMinSize: number,
    SeparatorTextBorderSize: number,
    ImageBorderSize: number,

    UseScreenGUIs: boolean,
    IgnoreGuiInset: boolean,
    ScreenInsets: Enum.ScreenInsets,
    Parent: BasePlayerGui,
    RichText: boolean,
    TextWrapped: boolean,
    DisplayOrderOffset: number,
    ZIndexOffset: number,

    MouseDoubleClickTime: number,
    MouseDoubleClickMaxDist: number,
    MouseDragThreshold: number,
}

type WidgetCall<W, A, S, E...> = (arguments: A, states: S, E...) -> W

export type Iris = {
    --[[
        -----------
          WIDGETS
        -----------
    ]]

    End: () -> (),

    -- Window API
    Window: WidgetCall<Window, WidgetArguments, WidgetStates?>,
    Tooltip: WidgetCall<Tooltip, WidgetArguments, nil>,

    -- Menu Widget API
    MenuBar: () -> Widget,
    Menu: WidgetCall<Menu, WidgetArguments, WidgetStates?>,
    MenuItem: WidgetCall<MenuItem, WidgetArguments, nil>,
    MenuToggle: WidgetCall<MenuToggle, WidgetArguments, WidgetStates?>,

    -- Format Widget API
    Separator: () -> Separator,
    Indent: (arguments: WidgetArguments?) -> Indent,
    SameLine: (arguments: WidgetArguments?) -> SameLine,
    Group: () -> Group,

    -- Text Widget API
    Text: WidgetCall<Text, WidgetArguments, nil>,
    TextWrapped: WidgetCall<Text, WidgetArguments, nil>,
    TextColored: WidgetCall<Text, WidgetArguments, nil>,
    SeparatorText: WidgetCall<SeparatorText, WidgetArguments, nil>,
    InputText: WidgetCall<InputText, WidgetArguments, WidgetStates?>,

    -- Basic Widget API
    Button: WidgetCall<Button, WidgetArguments, nil>,
    SmallButton: WidgetCall<Button, WidgetArguments, nil>,
    Checkbox: WidgetCall<Checkbox, WidgetArguments, WidgetStates?>,
    RadioButton: WidgetCall<RadioButton, WidgetArguments, WidgetStates?>,

    -- Tree Widget API
    Tree: WidgetCall<Tree, WidgetArguments, WidgetStates?>,
    CollapsingHeader: WidgetCall<CollapsingHeader, WidgetArguments, WidgetStates?>,

    -- Tab Widget API
    TabBar: WidgetCall<TabBar, WidgetArguments?, WidgetStates?>,
    Tab: WidgetCall<Tab, WidgetArguments, WidgetStates?>,

    -- Input Widget API
    InputNum: WidgetCall<Input<number>, WidgetArguments, WidgetStates?>,
    InputVector2: WidgetCall<Input<Vector2>, WidgetArguments, WidgetStates?>,
    InputVector3: WidgetCall<Input<Vector3>, WidgetArguments, WidgetStates?>,
    InputUDim: WidgetCall<Input<UDim>, WidgetArguments, WidgetStates?>,
    InputUDim2: WidgetCall<Input<UDim2>, WidgetArguments, WidgetStates?>,
    InputRect: WidgetCall<Input<Rect>, WidgetArguments, WidgetStates?>,
    InputColor3: WidgetCall<InputColor3, WidgetArguments, WidgetStates?>,
    InputColor4: WidgetCall<InputColor4, WidgetArguments, WidgetStates?>,

    -- Drag Widget API
    DragNum: WidgetCall<Input<number>, WidgetArguments, WidgetStates?>,
    DragVector2: WidgetCall<Input<Vector2>, WidgetArguments, WidgetStates?>,
    DragVector3: WidgetCall<Input<Vector3>, WidgetArguments, WidgetStates?>,
    DragUDim: WidgetCall<Input<UDim>, WidgetArguments, WidgetStates?>,
    DragUDim2: WidgetCall<Input<UDim2>, WidgetArguments, WidgetStates?>,
    DragRect: WidgetCall<Input<Rect>, WidgetArguments, WidgetStates?>,

    -- Slider Widget API
    SliderNum: WidgetCall<Input<number>, WidgetArguments, WidgetStates?>,
    SliderVector2: WidgetCall<Input<Vector2>, WidgetArguments, WidgetStates?>,
    SliderVector3: WidgetCall<Input<Vector3>, WidgetArguments, WidgetStates?>,
    SliderUDim: WidgetCall<Input<UDim>, WidgetArguments, WidgetStates?>,
    SliderUDim2: WidgetCall<Input<UDim2>, WidgetArguments, WidgetStates?>,
    SliderRect: WidgetCall<Input<Rect>, WidgetArguments, WidgetStates?>,

    -- Combo Widget Widget API
    Selectable: WidgetCall<Selectable, WidgetArguments, WidgetStates?>,
    Combo: WidgetCall<Combo, WidgetArguments, WidgetStates?>,
    ComboArray: WidgetCall<Combo, WidgetArguments, WidgetStates?, { any }>,
    ComboEnum: WidgetCall<Combo, WidgetArguments, WidgetStates?, Enum>,
    InputEnum: WidgetCall<Combo, WidgetArguments, WidgetStates?, Enum>,

    ProgressBar: WidgetCall<ProgressBar, WidgetArguments, WidgetStates?>,
    PlotLines: WidgetCall<PlotLines, WidgetArguments, WidgetStates?>,
    PlotHistogram: WidgetCall<PlotHistogram, WidgetArguments, WidgetStates?>,

    Image: WidgetCall<Image, WidgetArguments, nil>,
    ImageButton: WidgetCall<ImageButton, WidgetArguments, nil>,

    -- Table Widget Api
    Table: WidgetCall<Table, WidgetArguments, WidgetStates?>,
    NextColumn: () -> number,
    NextRow: () -> number,
    SetColumnIndex: (index: number) -> (),
    SetRowIndex: (index: number) -> (),
    NextHeaderColumn: () -> number,
    SetHeaderColumnIndex: (index: number) -> (),
    SetColumnWidth: (index: number, width: number) -> (),

    --[[
        ---------
          STATE
        ---------
    ]]

    State: <T>(initialValue: T) -> State<T>,
    WeakState: <T>(initialValue: T) -> T,
    VariableState: <T>(variable: T, callback: (T) -> ()) -> State<T>,
    TableState: <K, V>(tab: { [K]: V }, key: K, callback: ((newValue: V) -> true?)?) -> State<V>,
    ComputedState: <T, U>(firstState: State<T>, onChangeCallback: (firstValue: T) -> U) -> State<U>,

    --[[
        -------------
          FUNCTIONS
        -------------
    ]]

    Init: (parentInstance: BasePlayerGui | GuiBase2d?, eventConnection: (RBXScriptSignal | (() -> number) | false)?, allowMultipleInits: boolean) -> Iris,
    Shutdown: () -> (),
    Connect: (self: Iris, callback: () -> ()) -> () -> (),
    Append: (userInstance: GuiObject) -> (),
    ForceRefresh: () -> (),

    -- Widget
    SetFocusedWindow: (thisWidget: Window?) -> (),

    -- ID API
    PushId: (ID: ID) -> (),
    PopId: () -> (),
    SetNextWidgetID: (ID: ID) -> (),

    -- Config API
    UpdateGlobalConfig: (deltaStyle: { [string]: any }) -> (),
    PushConfig: (deltaStyle: { [string]: any }) -> (),
    PopConfig: () -> (),

    --[[
        --------------
          PROPERTIES
        --------------
    ]]

    Internal: Internal,
    Disabled: boolean,
    Args: { [string]: { [string]: number } },
    Events: { [string]: () -> boolean },

    TemplateConfig: { [string]: Config },
    _config: Config,
    ShowDemoWindow: () -> Window,
}

return {}


--[=[
    @within Iris
    @type ID string
]=]
export type ID = string

--[=[
    @within State
    @type State<T> { ID: ID, value: T, get: (self) -> T, set: (self, newValue: T) -> T, onChange: (self, callback: (newValue: T) -> ()) -> (), ConnectedWidgets: { [ID]: Widget }, ConnectedFunctions: { (newValue: T) -> () } }
]=]
export type State<T> = {
    ID: ID,
    value: T,
    lastChangeTick: number,
    ConnectedWidgets: { [ID]: Widget },
    ConnectedFunctions: { (newValue: T) -> () },

    get: (self: State<T>) -> T,
    set: (self: State<T>, newValue: T, force: true?) -> (),
    onChange: (self: State<T>, funcToConnect: (newValue: T) -> ()) -> () -> (),
    changed: (self: State<T>) -> boolean,
}

--[=[
    @within Iris
    @type Widget { ID: ID, type: string, lastCycleTick: number, parentWidget: Widget, Instance: GuiObject, ZIndex: number, arguments: { [string]: any }}
]=]
export type Widget = {
    ID: ID,
    type: string,
    lastCycleTick: number,
    trackedEvents: {},
    parentWidget: ParentWidget,

    arguments: {},
    providedArguments: {},

    Instance: GuiObject,
    ZIndex: number,
}

export type ParentWidget = Widget & {
    ChildContainer: GuiObject,
    ZOffset: number,
    ZUpdate: boolean,
}

export type StateWidget = Widget & {
    state: {
        [string]: State<any>,
    },
}

-- Events

export type Hovered = {
    isHoveredEvent: boolean,
    hovered: () -> boolean,
}

export type Clicked = {
    lastClickedTick: number,
    clicked: () -> boolean,
}

export type RightClicked = {
    lastRightClickedTick: number,
    rightClicked: () -> boolean,
}

export type DoubleClicked = {
    lastClickedTime: number,
    lastClickedPosition: Vector2,
    lastDoubleClickedTick: number,
    doubleClicked: () -> boolean,
}

export type CtrlClicked = {
    lastCtrlClickedTick: number,
    ctrlClicked: () -> boolean,
}

export type Active = {
    active: () -> boolean,
}

export type Checked = {
    lastCheckedTick: number,
    checked: () -> boolean,
}

export type Unchecked = {
    lastUncheckedTick: number,
    unchecked: () -> boolean,
}

export type Opened = {
    lastOpenedTick: number,
    opened: () -> boolean,
}

export type Closed = {
    lastClosedTick: number,
    closed: () -> boolean,
}

export type Collapsed = {
    lastCollapsedTick: number,
    collapsed: () -> boolean,
}

export type Uncollapsed = {
    lastUncollapsedTick: number,
    uncollapsed: () -> boolean,
}

export type Selected = {
    lastSelectedTick: number,
    selected: () -> boolean,
}

export type Unselected = {
    lastUnselectedTick: number,
    unselected: () -> boolean,
}

export type Changed = {
    lastChangedTick: number,
    changed: () -> boolean,
}

export type NumberChanged = {
    lastNumberChangedTick: number,
    numberChanged: () -> boolean,
}

export type TextChanged = {
    lastTextChangedTick: number,
    textChanged: () -> boolean,
}

-- Widgets

-- Window

export type Root = ParentWidget

export type Window = ParentWidget & {
    usesScreenGuis: boolean,

    arguments: {
        Title: string?,
        NoTitleBar: boolean?,
        NoBackground: boolean?,
        NoCollapse: boolean?,
        NoClose: boolean?,
        NoMove: boolean?,
        NoScrollbar: boolean?,
        NoResize: boolean?,
        NoNav: boolean?,
        NoMenu: boolean?,
    },

    state: {
        size: State<Vector2>,
        position: State<Vector2>,
        isUncollapsed: State<boolean>,
        isOpened: State<boolean>,
        scrollDistance: State<number>,
    },
} & Opened & Closed & Collapsed & Uncollapsed & Hovered

export type Tooltip = Widget & {
    arguments: {
        Text: string,
    },
}

-- Menu

export type MenuBar = ParentWidget

export type Menu = ParentWidget & {
    ButtonColors: { [string]: Color3 | number },

    arguments: {
        Text: string?,
    },

    state: {
        isOpened: State<boolean>,
    },
} & Clicked & Opened & Closed & Hovered

export type MenuItem = Widget & {
    arguments: {
        Text: string,
        KeyCode: Enum.KeyCode?,
        ModifierKey: Enum.ModifierKey?,
    },
} & Clicked & Hovered

export type MenuToggle = Widget & {
    arguments: {
        Text: string,
        KeyCode: Enum.KeyCode?,
        ModifierKey: Enum.ModifierKey?,
    },

    state: {
        isChecked: State<boolean>,
    },
} & Checked & Unchecked & Hovered

-- Format

export type Separator = Widget

export type Indent = ParentWidget & {
    arguments: {
        Width: number?,
    },
}

export type SameLine = ParentWidget & {
    arguments: {
        Width: number?,
        VerticalAlignment: Enum.VerticalAlignment?,
        HorizontalAlignment: Enum.HorizontalAlignment?,
    },
}

export type Group = ParentWidget

-- Text

export type Text = Widget & {
    arguments: {
        Text: string,
        Wrapped: boolean?,
        Color: Color3?,
        RichText: boolean?,
    },
} & Hovered

export type SeparatorText = Widget & {
    arguments: {
        Text: string,
    },
} & Hovered

-- Basic

export type Button = Widget & {
    arguments: {
        Text: string?,
        Size: UDim2?,
    },
} & Clicked & RightClicked & DoubleClicked & CtrlClicked & Hovered

export type Checkbox = Widget & {
    arguments: {
        Text: string?,
    },

    state: {
        isChecked: State<boolean>,
    },
} & Unchecked & Checked & Hovered

export type RadioButton = Widget & {
    arguments: {
        Text: string?,
        Index: any,
    },

    state: {
        index: State<any>,
    },

    active: () -> boolean,
} & Selected & Unselected & Active & Hovered

-- Image

export type Image = Widget & {
    arguments: {
        Image: string,
        Size: UDim2,
        Rect: Rect?,
        ScaleType: Enum.ScaleType?,
        TileSize: UDim2?,
        SliceCenter: Rect?,
        SliceScale: number?,
        ResampleMode: Enum.ResamplerMode?,
    },
} & Hovered

-- ooops, may have overriden a Roblox type, and then got a weird type message
-- let's just hope I don't have to use a Roblox ImageButton type anywhere by name in this file
export type ImageButton = Image & Clicked & RightClicked & DoubleClicked & CtrlClicked

-- Tree

export type Tree = CollapsingHeader & {
    arguments: {
        Text: string,
        SpanAvailWidth: boolean?,
        NoIndent: boolean?,
        DefaultOpen: true?,
    },
}

export type CollapsingHeader = ParentWidget & {
    arguments: {
        Text: string?,
        DefaultOpen: true?,
    },

    state: {
        isUncollapsed: State<boolean>,
    },
} & Collapsed & Uncollapsed & Hovered

-- Tabs

export type TabBar = ParentWidget & {
    Tabs: { Tab },

    state: {
        index: State<number>,
    },
}

export type Tab = ParentWidget & {
    parentWidget: TabBar,
    Index: number,
    ButtonColors: { [string]: Color3 | number },

    arguments: {
        Text: string,
        Hideable: boolean,
    },

    state: {
        index: State<number>,
        isOpened: State<boolean>,
    },
} & Clicked & Opened & Selected & Unselected & Active & Closed & Hovered

-- Input
export type Input<T> = Widget & {
    lastClickedTime: number,
    lastClickedPosition: Vector2,

    arguments: {
        Text: string?,
        Increment: T,
        Min: T,
        Max: T,
        Format: { string },
        Prefix: { string },
        NoButtons: boolean?,
    },

    state: {
        number: State<T>,
        editingText: State<number>,
    },
} & NumberChanged & Hovered

export type InputColor3 = Input<{ number }> & {
    arguments: {
        UseFloats: boolean?,
        UseHSV: boolean?,
    },

    state: {
        color: State<Color3>,
        editingText: State<boolean>,
    },
} & NumberChanged & Hovered

export type InputColor4 = InputColor3 & {
    state: {
        transparency: State<number>,
    },
}

export type InputEnum = Input<number> & {
    state: {
        enumItem: State<EnumItem>,
    },
}

export type InputText = Widget & {
    arguments: {
        Text: string?,
        TextHint: string?,
        ReadOnly: boolean?,
        MultiLine: boolean?,
    },

    state: {
        text: State<string>,
    },
} & TextChanged & Hovered

-- Combo

export type Selectable = Widget & {
    ButtonColors: { [string]: Color3 | number },

    arguments: {
        Text: string?,
        Index: any?,
        NoClick: boolean?,
    },

    state: {
        index: State<any>,
    },
} & Selected & Unselected & Clicked & RightClicked & DoubleClicked & CtrlClicked & Hovered

export type Combo = ParentWidget & {
    arguments: {
        Text: string?,
        NoButton: boolean?,
        NoPreview: boolean?,
    },

    state: {
        index: State<any>,
        isOpened: State<boolean>,
    },

    UIListLayout: UIListLayout,
} & Opened & Closed & Changed & Clicked & Hovered

-- Plot

export type ProgressBar = Widget & {
    arguments: {
        Text: string?,
        Format: string?,
    },

    state: {
        progress: State<number>,
    },
} & Changed & Hovered

export type PlotLines = Widget & {
    Lines: { Frame },
    HoveredLine: Frame | false,
    Tooltip: TextLabel,

    arguments: {
        Text: string,
        Height: number,
        Min: number,
        Max: number,
        TextOverlay: string,
    },

    state: {
        values: State<{ number }>,
        hovered: State<{ number }?>,
    },
} & Hovered

export type PlotHistogram = Widget & {
    Blocks: { Frame },
    HoveredBlock: Frame | false,
    Tooltip: TextLabel,

    arguments: {
        Text: string,
        Height: number,
        Min: number,
        Max: number,
        TextOverlay: string,
        BaseLine: number,
    },

    state: {
        values: State<{ number }>,
        hovered: State<number?>,
    },
} & Hovered

export type Table = ParentWidget & {
    _columnIndex: number,
    _rowIndex: number,
    _rowContainer: Frame,
    _rowInstances: { Frame },
    _cellInstances: { { Frame } },
    _rowBorders: { Frame },
    _columnBorders: { GuiButton },
    _rowCycles: { number },
    _widths: { UDim },
    _minWidths: { number },

    arguments: {
        NumColumns: number,
        Header: boolean,
        RowBackground: boolean,
        OuterBorders: boolean,
        InnerBorders: boolean,
        Resizable: boolean,
        FixedWidth: boolean,
        ProportionalWidth: boolean,
        LimitTableWidth: boolean,
    },

    state: {
        widths: State<{ number }>,
    },
} & Hovered

return {}


local TemplateConfig = {
    colorDark = { -- Dear, ImGui default dark
        TextColor = Color3.fromRGB(255, 255, 255),
        TextTransparency = 0,
        TextDisabledColor = Color3.fromRGB(128, 128, 128),
        TextDisabledTransparency = 0,

        -- Dear ImGui uses 110, 110, 125
        -- The Roblox window selection highlight is 67, 191, 254
        BorderColor = Color3.fromRGB(110, 110, 125),
        BorderTransparency = 0.5,
        BorderActiveColor = Color3.fromRGB(160, 160, 175), -- does not exist in Dear ImGui
        BorderActiveTransparency = 0.3,

        WindowBgColor = Color3.fromRGB(15, 15, 15),
        WindowBgTransparency = 0.06,
        PopupBgColor = Color3.fromRGB(20, 20, 20),
        PopupBgTransparency = 0.06,

        ScrollbarGrabColor = Color3.fromRGB(79, 79, 79),
        ScrollbarGrabTransparency = 0,

        TitleBgColor = Color3.fromRGB(10, 10, 10),
        TitleBgTransparency = 0,
        TitleBgActiveColor = Color3.fromRGB(41, 74, 122),
        TitleBgActiveTransparency = 0,
        TitleBgCollapsedColor = Color3.fromRGB(0, 0, 0),
        TitleBgCollapsedTransparency = 0.5,

        MenubarBgColor = Color3.fromRGB(36, 36, 36),
        MenubarBgTransparency = 0,

        FrameBgColor = Color3.fromRGB(41, 74, 122),
        FrameBgTransparency = 0.46,
        FrameBgHoveredColor = Color3.fromRGB(66, 150, 250),
        FrameBgHoveredTransparency = 0.46,
        FrameBgActiveColor = Color3.fromRGB(66, 150, 250),
        FrameBgActiveTransparency = 0.33,

        ButtonColor = Color3.fromRGB(66, 150, 250),
        ButtonTransparency = 0.6,
        ButtonHoveredColor = Color3.fromRGB(66, 150, 250),
        ButtonHoveredTransparency = 0,
        ButtonActiveColor = Color3.fromRGB(15, 135, 250),
        ButtonActiveTransparency = 0,

        ImageColor = Color3.fromRGB(255, 255, 255),
        ImageTransparency = 0,

        SliderGrabColor = Color3.fromRGB(66, 150, 250),
        SliderGrabTransparency = 0,
        SliderGrabActiveColor = Color3.fromRGB(66, 150, 250),
        SliderGrabActiveTransparency = 0,

        HeaderColor = Color3.fromRGB(66, 150, 250),
        HeaderTransparency = 0.69,
        HeaderHoveredColor = Color3.fromRGB(66, 150, 250),
        HeaderHoveredTransparency = 0.2,
        HeaderActiveColor = Color3.fromRGB(66, 150, 250),
        HeaderActiveTransparency = 0,

        TabColor = Color3.fromRGB(46, 89, 148),
        TabTransparency = 0.14,
        TabHoveredColor = Color3.fromRGB(66, 150, 250),
        TabHoveredTransparency = 0.2,
        TabActiveColor = Color3.fromRGB(51, 105, 173),
        TabActiveTransparency = 0,

        SelectionImageObjectColor = Color3.fromRGB(255, 255, 255),
        SelectionImageObjectTransparency = 0.8,
        SelectionImageObjectBorderColor = Color3.fromRGB(255, 255, 255),
        SelectionImageObjectBorderTransparency = 0,

        TableBorderStrongColor = Color3.fromRGB(79, 79, 89),
        TableBorderStrongTransparency = 0,
        TableBorderLightColor = Color3.fromRGB(59, 59, 64),
        TableBorderLightTransparency = 0,
        TableRowBgColor = Color3.fromRGB(0, 0, 0),
        TableRowBgTransparency = 1,
        TableRowBgAltColor = Color3.fromRGB(255, 255, 255),
        TableRowBgAltTransparency = 0.94,
        TableHeaderColor = Color3.fromRGB(48, 48, 51),
        TableHeaderTransparency = 0,

        NavWindowingHighlightColor = Color3.fromRGB(255, 255, 255),
        NavWindowingHighlightTransparency = 0.3,
        NavWindowingDimBgColor = Color3.fromRGB(204, 204, 204),
        NavWindowingDimBgTransparency = 0.65,

        SeparatorColor = Color3.fromRGB(110, 110, 128),
        SeparatorTransparency = 0.5,

        CheckMarkColor = Color3.fromRGB(66, 150, 250),
        CheckMarkTransparency = 0,

        PlotLinesColor = Color3.fromRGB(156, 156, 156),
        PlotLinesTransparency = 0,
        PlotLinesHoveredColor = Color3.fromRGB(255, 110, 89),
        PlotLinesHoveredTransparency = 0,
        PlotHistogramColor = Color3.fromRGB(230, 179, 0),
        PlotHistogramTransparency = 0,
        PlotHistogramHoveredColor = Color3.fromRGB(255, 153, 0),
        PlotHistogramHoveredTransparency = 0,

        ResizeGripColor = Color3.fromRGB(66, 150, 250),
        ResizeGripTransparency = 0.8,
        ResizeGripHoveredColor = Color3.fromRGB(66, 150, 250),
        ResizeGripHoveredTransparency = 0.33,
        ResizeGripActiveColor = Color3.fromRGB(66, 150, 250),
        ResizeGripActiveTransparency = 0.05,
    },
    colorLight = { -- Dear, ImGui default light
        TextColor = Color3.fromRGB(0, 0, 0),
        TextTransparency = 0,
        TextDisabledColor = Color3.fromRGB(153, 153, 153),
        TextDisabledTransparency = 0,

        -- Dear ImGui uses 0, 0, 0, 77
        -- The Roblox window selection highlight is 67, 191, 254
        BorderColor = Color3.fromRGB(64, 64, 64),
        BorderActiveColor = Color3.fromRGB(64, 64, 64), -- does not exist in Dear ImGui

        -- BorderTransparency will be problematic for non UIStroke border implimentations
        -- will not be implimented because of this
        BorderTransparency = 0.5,
        BorderActiveTransparency = 0.2,

        WindowBgColor = Color3.fromRGB(240, 240, 240),
        WindowBgTransparency = 0,
        PopupBgColor = Color3.fromRGB(255, 255, 255),
        PopupBgTransparency = 0.02,

        TitleBgColor = Color3.fromRGB(245, 245, 245),
        TitleBgTransparency = 0,
        TitleBgActiveColor = Color3.fromRGB(209, 209, 209),
        TitleBgActiveTransparency = 0,
        TitleBgCollapsedColor = Color3.fromRGB(255, 255, 255),
        TitleBgCollapsedTransparency = 0.5,

        MenubarBgColor = Color3.fromRGB(219, 219, 219),
        MenubarBgTransparency = 0,

        ScrollbarGrabColor = Color3.fromRGB(176, 176, 176),
        ScrollbarGrabTransparency = 0.2,

        FrameBgColor = Color3.fromRGB(255, 255, 255),
        FrameBgTransparency = 0.6,
        FrameBgHoveredColor = Color3.fromRGB(66, 150, 250),
        FrameBgHoveredTransparency = 0.6,
        FrameBgActiveColor = Color3.fromRGB(66, 150, 250),
        FrameBgActiveTransparency = 0.33,

        ButtonColor = Color3.fromRGB(66, 150, 250),
        ButtonTransparency = 0.6,
        ButtonHoveredColor = Color3.fromRGB(66, 150, 250),
        ButtonHoveredTransparency = 0,
        ButtonActiveColor = Color3.fromRGB(15, 135, 250),
        ButtonActiveTransparency = 0,

        ImageColor = Color3.fromRGB(255, 255, 255),
        ImageTransparency = 0,

        HeaderColor = Color3.fromRGB(66, 150, 250),
        HeaderTransparency = 0.31,
        HeaderHoveredColor = Color3.fromRGB(66, 150, 250),
        HeaderHoveredTransparency = 0.2,
        HeaderActiveColor = Color3.fromRGB(66, 150, 250),
        HeaderActiveTransparency = 0,

        TabColor = Color3.fromRGB(195, 203, 213),
        TabTransparency = 0.07,
        TabHoveredColor = Color3.fromRGB(66, 150, 250),
        TabHoveredTransparency = 0.2,
        TabActiveColor = Color3.fromRGB(152, 186, 255),
        TabActiveTransparency = 0,

        SliderGrabColor = Color3.fromRGB(61, 133, 224),
        SliderGrabTransparency = 0,
        SliderGrabActiveColor = Color3.fromRGB(117, 138, 204),
        SliderGrabActiveTransparency = 0,

        SelectionImageObjectColor = Color3.fromRGB(0, 0, 0),
        SelectionImageObjectTransparency = 0.8,
        SelectionImageObjectBorderColor = Color3.fromRGB(0, 0, 0),
        SelectionImageObjectBorderTransparency = 0,

        TableBorderStrongColor = Color3.fromRGB(145, 145, 163),
        TableBorderStrongTransparency = 0,
        TableBorderLightColor = Color3.fromRGB(173, 173, 189),
        TableBorderLightTransparency = 0,
        TableRowBgColor = Color3.fromRGB(0, 0, 0),
        TableRowBgTransparency = 1,
        TableRowBgAltColor = Color3.fromRGB(77, 77, 77),
        TableRowBgAltTransparency = 0.91,
        TableHeaderColor = Color3.fromRGB(199, 222, 250),
        TableHeaderTransparency = 0,

        NavWindowingHighlightColor = Color3.fromRGB(179, 179, 179),
        NavWindowingHighlightTransparency = 0.3,
        NavWindowingDimBgColor = Color3.fromRGB(51, 51, 51),
        NavWindowingDimBgTransparency = 0.8,

        SeparatorColor = Color3.fromRGB(99, 99, 99),
        SeparatorTransparency = 0.38,

        CheckMarkColor = Color3.fromRGB(66, 150, 250),
        CheckMarkTransparency = 0,

        PlotLinesColor = Color3.fromRGB(99, 99, 99),
        PlotLinesTransparency = 0,
        PlotLinesHoveredColor = Color3.fromRGB(255, 110, 89),
        PlotLinesHoveredTransparency = 0,
        PlotHistogramColor = Color3.fromRGB(230, 179, 0),
        PlotHistogramTransparency = 0,
        PlotHistogramHoveredColor = Color3.fromRGB(255, 153, 0),
        PlotHistogramHoveredTransparency = 0,

        ResizeGripColor = Color3.fromRGB(89, 89, 89),
        ResizeGripTransparency = 0.83,
        ResizeGripHoveredColor = Color3.fromRGB(66, 150, 250),
        ResizeGripHoveredTransparency = 0.33,
        ResizeGripActiveColor = Color3.fromRGB(66, 150, 250),
        ResizeGripActiveTransparency = 0.05,
    },

    sizeDefault = { -- Dear, ImGui default
        ItemWidth = UDim.new(1, 0),
        ContentWidth = UDim.new(0.65, 0),
        ContentHeight = UDim.new(0, 0),

        WindowPadding = Vector2.new(8, 8),
        WindowResizePadding = Vector2.new(6, 6),
        FramePadding = Vector2.new(4, 3),
        ItemSpacing = Vector2.new(8, 4),
        ItemInnerSpacing = Vector2.new(4, 4),
        CellPadding = Vector2.new(4, 2),
        DisplaySafeAreaPadding = Vector2.new(0, 0),
        SeparatorTextPadding = Vector2.new(20, 3),
        IndentSpacing = 21,

        TextFont = Font.fromEnum(Enum.Font.Code),
        TextSize = 13,
        FrameBorderSize = 0,
        FrameRounding = 0,
        GrabRounding = 0,
        WindowRounding = 0, -- these don't actually work but it's nice to have them.
        WindowBorderSize = 1,
        WindowTitleAlign = Enum.LeftRight.Left,
        PopupBorderSize = 1,
        PopupRounding = 0,
        ScrollbarSize = 7,
        GrabMinSize = 10,
        SeparatorTextBorderSize = 3,
        ImageBorderSize = 2,
    },
    sizeClear = { -- easier to read and manuveure
        ItemWidth = UDim.new(1, 0),
        ContentWidth = UDim.new(0.65, 0),
        ContentHeight = UDim.new(0, 0),

        WindowPadding = Vector2.new(12, 8),
        WindowResizePadding = Vector2.new(8, 8),
        FramePadding = Vector2.new(6, 4),
        ItemSpacing = Vector2.new(8, 8),
        ItemInnerSpacing = Vector2.new(8, 8),
        CellPadding = Vector2.new(4, 4),
        DisplaySafeAreaPadding = Vector2.new(8, 8),
        SeparatorTextPadding = Vector2.new(24, 6),
        IndentSpacing = 25,

        TextFont = Font.fromEnum(Enum.Font.Ubuntu),
        TextSize = 15,
        FrameBorderSize = 1,
        FrameRounding = 4,
        GrabRounding = 4,
        WindowRounding = 4,
        WindowBorderSize = 1,
        WindowTitleAlign = Enum.LeftRight.Center,
        PopupBorderSize = 1,
        PopupRounding = 4,
        ScrollbarSize = 9,
        GrabMinSize = 14,
        SeparatorTextBorderSize = 4,
        ImageBorderSize = 4,
    },

    utilityDefault = {
        UseScreenGUIs = true,
        IgnoreGuiInset = false,
        ScreenInsets = Enum.ScreenInsets.CoreUISafeInsets,
        Parent = nil,
        RichText = false,
        TextWrapped = false,
        DisplayOrderOffset = 127,
        ZIndexOffset = 0,

        MouseDoubleClickTime = 0.30, -- Time for a double-click, in seconds.
        MouseDoubleClickMaxDist = 6.0, -- Distance threshold to stay in to validate a double-click, in pixels.

        HoverColor = Color3.fromRGB(255, 255, 0),
        HoverTransparency = 0.1,
    },
}

return TemplateConfig


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

return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
    local abstractButton = {
        hasState = false,
        hasChildren = false,
        Args = {
            ["Text"] = 1,
            ["Size"] = 2,
        },
        Events = {
            ["clicked"] = widgets.EVENTS.click(function(thisWidget: Types.Widget)
                return thisWidget.Instance
            end),
            ["rightClicked"] = widgets.EVENTS.rightClick(function(thisWidget: Types.Widget)
                return thisWidget.Instance
            end),
            ["doubleClicked"] = widgets.EVENTS.doubleClick(function(thisWidget: Types.Widget)
                return thisWidget.Instance
            end),
            ["ctrlClicked"] = widgets.EVENTS.ctrlClick(function(thisWidget: Types.Widget)
                return thisWidget.Instance
            end),
            ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Types.Widget)
                return thisWidget.Instance
            end),
        },
        Generate = function(_thisWidget: Types.Button)
            local Button = Instance.new("TextButton")
            Button.AutomaticSize = Enum.AutomaticSize.XY
            Button.Size = UDim2.fromOffset(0, 0)
            Button.BackgroundColor3 = Iris._config.ButtonColor
            Button.BackgroundTransparency = Iris._config.ButtonTransparency
            Button.AutoButtonColor = false

            widgets.applyTextStyle(Button)
            Button.TextXAlignment = Enum.TextXAlignment.Center

            widgets.applyFrameStyle(Button)

            widgets.applyInteractionHighlights("Background", Button, Button, {
                Color = Iris._config.ButtonColor,
                Transparency = Iris._config.ButtonTransparency,
                HoveredColor = Iris._config.ButtonHoveredColor,
                HoveredTransparency = Iris._config.ButtonHoveredTransparency,
                ActiveColor = Iris._config.ButtonActiveColor,
                ActiveTransparency = Iris._config.ButtonActiveTransparency,
            })

            return Button
        end,
        Update = function(thisWidget: Types.Button)
            local Button = thisWidget.Instance :: TextButton
            Button.Text = thisWidget.arguments.Text or "Button"
            Button.Size = thisWidget.arguments.Size or UDim2.fromOffset(0, 0)
        end,
        Discard = function(thisWidget: Types.Button)
            thisWidget.Instance:Destroy()
        end,
    } :: Types.WidgetClass
    widgets.abstractButton = abstractButton

    --stylua: ignore
    Iris.WidgetConstructor("Button", widgets.extend(abstractButton, {
            Generate = function(thisWidget: Types.Button)
                local Button = abstractButton.Generate(thisWidget)
                Button.Name = "Iris_Button"

                return Button
            end,
        } :: Types.WidgetClass)
    )

    --stylua: ignore
    Iris.WidgetConstructor("SmallButton", widgets.extend(abstractButton, {
            Generate = function(thisWidget: Types.Button)
                local SmallButton = abstractButton.Generate(thisWidget)
                SmallButton.Name = "Iris_SmallButton"

                local uiPadding: UIPadding = SmallButton.UIPadding
                uiPadding.PaddingLeft = UDim.new(0, 2)
                uiPadding.PaddingRight = UDim.new(0, 2)
                uiPadding.PaddingTop = UDim.new(0, 0)
                uiPadding.PaddingBottom = UDim.new(0, 0)

                return SmallButton
            end,
        } :: Types.WidgetClass)
    )
end


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

return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
    --stylua: ignore
    Iris.WidgetConstructor("Checkbox", {
        hasState = true,
        hasChildren = false,
        Args = {
            ["Text"] = 1,
        },
        Events = {
            ["checked"] = {
                ["Init"] = function(_thisWidget: Types.Checkbox) end,
                ["Get"] = function(thisWidget: Types.Checkbox)
                    return thisWidget.lastCheckedTick == Iris._cycleTick
                end,
            },
            ["unchecked"] = {
                ["Init"] = function(_thisWidget: Types.Checkbox) end,
                ["Get"] = function(thisWidget: Types.Checkbox)
                    return thisWidget.lastUncheckedTick == Iris._cycleTick
                end,
            },
            ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Types.Widget)
                return thisWidget.Instance
            end),
        },
        Generate = function(thisWidget: Types.Checkbox)
            local Checkbox = Instance.new("TextButton")
            Checkbox.Name = "Iris_Checkbox"
            Checkbox.AutomaticSize = Enum.AutomaticSize.XY
            Checkbox.Size = UDim2.fromOffset(0, 0)
            Checkbox.BackgroundTransparency = 1
            Checkbox.BorderSizePixel = 0
            Checkbox.Text = ""
            Checkbox.AutoButtonColor = false

            widgets.UIListLayout(Checkbox, Enum.FillDirection.Horizontal, UDim.new(0, Iris._config.ItemInnerSpacing.X)).VerticalAlignment = Enum.VerticalAlignment.Center

            local checkboxSize = Iris._config.TextSize + 2 * Iris._config.FramePadding.Y

            local Box = Instance.new("Frame")
            Box.Name = "Box"
            Box.Size = UDim2.fromOffset(checkboxSize, checkboxSize)
            Box.BackgroundColor3 = Iris._config.FrameBgColor
            Box.BackgroundTransparency = Iris._config.FrameBgTransparency

            widgets.applyFrameStyle(Box, true)
            widgets.UIPadding(Box, Vector2.new(math.floor(checkboxSize / 10), math.floor(checkboxSize / 10)))

            widgets.applyInteractionHighlights("Background", Checkbox, Box, {
                Color = Iris._config.FrameBgColor,
                Transparency = Iris._config.FrameBgTransparency,
                HoveredColor = Iris._config.FrameBgHoveredColor,
                HoveredTransparency = Iris._config.FrameBgHoveredTransparency,
                ActiveColor = Iris._config.FrameBgActiveColor,
                ActiveTransparency = Iris._config.FrameBgActiveTransparency,
            })

            Box.Parent = Checkbox

            local Checkmark = Instance.new("ImageLabel")
            Checkmark.Name = "Checkmark"
            Checkmark.Size = UDim2.fromScale(1, 1)
            Checkmark.BackgroundTransparency = 1
            Checkmark.Image = widgets.ICONS.CHECKMARK
            Checkmark.ImageColor3 = Iris._config.CheckMarkColor
            Checkmark.ImageTransparency = 1
            Checkmark.ScaleType = Enum.ScaleType.Fit

            Checkmark.Parent = Box

            widgets.applyButtonClick(Checkbox, function()
                thisWidget.state.isChecked:set(not thisWidget.state.isChecked.value)
            end)

            local TextLabel = Instance.new("TextLabel")
            TextLabel.Name = "TextLabel"
            TextLabel.AutomaticSize = Enum.AutomaticSize.XY
            TextLabel.BackgroundTransparency = 1
            TextLabel.BorderSizePixel = 0
            TextLabel.LayoutOrder = 1

            widgets.applyTextStyle(TextLabel)
            TextLabel.Parent = Checkbox

            return Checkbox
        end,
        GenerateState = function(thisWidget: Types.Checkbox)
            if thisWidget.state.isChecked == nil then
                thisWidget.state.isChecked = Iris._widgetState(thisWidget, "checked", false)
            end
        end,
        Update = function(thisWidget: Types.Checkbox)
            local Checkbox = thisWidget.Instance :: TextButton
            Checkbox.TextLabel.Text = thisWidget.arguments.Text or "Checkbox"
        end,
        UpdateState = function(thisWidget: Types.Checkbox)
            local Checkbox = thisWidget.Instance :: TextButton
            local Box = Checkbox.Box :: Frame
            local Checkmark: ImageLabel = Box.Checkmark
            if thisWidget.state.isChecked.value then
                Checkmark.ImageTransparency = Iris._config.CheckMarkTransparency
                thisWidget.lastCheckedTick = Iris._cycleTick + 1
            else
                Checkmark.ImageTransparency = 1
                thisWidget.lastUncheckedTick = Iris._cycleTick + 1
            end
        end,
        Discard = function(thisWidget: Types.Checkbox)
            thisWidget.Instance:Destroy()
            widgets.discardState(thisWidget)
        end,
    } :: Types.WidgetClass)
end