local function Init()
    local metadata = {
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return ReactiveValue|nil
        __add = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value + other._value
            end
            if otherType == "string" and self._type == otherType then
                return self._value .. other
            end
            if otherType == "number" and self._type == otherType then
                return self._value + other
            end
            return nil
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return ReactiveValue|nil
        __mul = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value * other._value
            end
            if otherType == "number" and self._type == otherType then
                return self._value * other
            end
            return nil
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return ReactiveValue|nil
        __sub = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value - other._value
            end
            if otherType == "number" and self._type == otherType then
                return self._value - other
            end
            return nil
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return ReactiveValue|nil
        __div = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value / other._value
            end
            if otherType == "number" and self._type == otherType then
                return self._value / other
            end
            return nil
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return ReactiveValue|nil
        __mod = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value % other._value
            end
            if otherType == "number" and self._type == otherType then
                return self._value % other
            end
            return nil
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return ReactiveValue|nil
        __pow = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value ^ other._value
            end
            if otherType == "number" and self._type == otherType then
                return self._value ^ other
            end
            return nil
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return boolean
        __eq = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value == other._value
            end
            return self._value == other
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return boolean
        __lt = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value < other._value
            end
            return self._value < other
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return boolean
        __le = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value <= other._value
            end
            return self._value <= other
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return boolean
        __gt = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value > other._value
            end
            return self._value > other
        end,
        ---@param self ReactiveValue
        ---@param other ReactiveValue
        ---@return boolean
        __ge = function(self, other)
            local otherType = type(other)
            if otherType == "table" and other._type and other._type == self._type and other._value then
                return self._value >= other._value
            end
            return self._value >= other
        end,
        ---@param self ReactiveValue
        ---@return number
        __len = function(self)
            if self._type == "table" then
                return #self._value
            end
            if self._type == "string" then
                return string.len(self._value)
            end
            return 0
        end,
        ---@param self ReactiveValue
        ---@return string
        __tostring = function(self)
            return tostring(self._value)
        end,
        ---@param self ReactiveValue
        ---@param key string
        ---@param value any
        ---@return nil
        __newindex = function(self, key, value)
            local setupComplete = rawget(self, "_setupComplete")
            if setupComplete == nil or setupComplete == false then
                rawset(self, key, value)
                return
            end
            if self._type ~= "table" then
                rawset(self, key, value)
                return
            end
            self._value[key] = value
            local ChangedKey = { key }
            -- If the value being assigned is a ReactiveValue
            -- Then listen to changes on it as well
            -- And propagate those changes upwards
            if self._recursive and getmetatable(value) == getmetatable(self) then
                self:_setupListeners(key, value)
            end
            self:_notify()
            self:_notifyFieldChanged(ChangedKey)
            self:_notifyAnyFieldChanged(ChangedKey)
        end,
        ---@param self ReactiveValue
        ---@param key string
        ---@return any|nil
        __index = function(self, key)
            local value = rawget(self, key)
            if value ~= nil then
                return value
            end
            if rawget(self, "_type") ~= "table" then
                return nil
            end
            local innerTable = rawget(self, "_value")
            if innerTable ~= nil then
                return rawget(innerTable, key)
            end
            return nil
        end
        -- __index = ReactiveValue
    }
    --- Sadly I could not get @generic to play nice with this class
    --- I think it's not ready yet, there are issues on github describing similar problems and it is marked as WIP...
    --- Guess I'll have to live without it for now and specify type of a RV in #type
    ---## A type safe value that can be listened to for changes
    ---### **Always use RV:set() for setting primitive values**
    --- Supports primitive values and tables
    --- Tables can be listened to for changes on any field or a specific field
    ---### Example usage (value):
    --- ```lua
    --- local test = ReactiveValue.new(1)
    --- test:onChange(function(value)
    ---     print("test changed to " .. value)
    --- end)
    --- test:set(2)
    --- test:set(test + 3)
    --- ```
    ---### Example usage (table):
    --- ```lua
    --- local test = ReactiveValue.new({1, 2, 3})
    --- test:onAnyFieldChange(function(field, value)
    ---     print(string.format("test.%s changed to %s", table.concat(field, "."), value))
    --- end)
    --- test[1] = 4 -- test.1 changed to 4
    --- test[4] = {1, 2, 3} -- test.4 changed to 
    --- test[4][1] = 14 -- No log(!!) because test[4] is a table and not a ReactiveValue
    --- ```
    ---### To trigger a callback for `test[4][1]` in the previous example do:
    --- ```lua
    --- local test = ReactiveValue.new({1, 2, 3}, true)
    --- test:onAnyFieldChange(function(field, value)
    ---     print(string.format("test.%s changed to %s", table.concat(field, "."), value))
    --- end)
    --- test[1] = 4 -- test.1 changed to 4
    --- test[4] = {1, 2, 3} -- test.4 changed to 
    --- test[4][1] = 14 -- test.4.1 changed to 14
    --- ```
    ---### To listen to a specific field of a table do:
    --- ```lua
    --- local test = ReactiveValue.new({1, 2, 3}, true)
    --- test:onFieldChange("1", function(value)
    ---     print("test.1 changed to " .. value)
    --- end)
    --- test[1] = 4 -- test.1 changed to 4
    --- test[4] = {1, 2, 3} -- Does not trigger callback
    -- ```
    ---@class ReactiveValue
    ---@field _listeners table
    ---@field _fieldListeners table>
    ---@field _anyFieldListeners table>
    ---@field _oneTimeListeners table
    ---@field _value any
    ---@field _type string
    ---@field _recursive boolean?
    ReactiveValue = {
        ---#### Get the underlying value of a ReactiveValue
        ---@param self ReactiveValue
        ---@return any
        get = function(self) end,
        ---### Set the underlying value of a ReactiveValue triggering listener callbacks
        ---@param self ReactiveValue
        ---@param newValue any
        set = function(self, newValue) end,
        ---## EVENT
        ---### Register a listener that is triggered whenever the underlying value changes
        --- Returns a function that can be called to undo the callback
        ---@param self ReactiveValue
        ---@param callback fun(value: any, type: string)
        ---@return fun(): nil
        onChange = function(self, callback) end,
        ---## EVENT
        ---### Register a listener that is triggered whenever a specific field of a table changes
        --- Returns a function that can be called to undo the callback
        ---@param self ReactiveValue
        ---@param field string
        ---@param callback fun(field: string[], value: any, type: string)
        ---@return fun(): nil
        onFieldChange = function(self, field, callback) end,
        ---## EVENT
        ---### Register a listener that is triggered whenever any field of a table changes
        --- Returns a function that can be called to undo the callback
        ---@param self ReactiveValue
        ---@param callback fun(field: string[], value: any, type: string)
        ---@param depth number? How deep to listen for changes
        ---@return fun(): nil
        onAnyFieldChange = function(self, callback, depth) end,
        ---## EVENT
        ---### Register a listener that is triggered ONCE whenever the underlying value changes
        --- Returns a function that can be called to undo the callback
        ---@param self ReactiveValue
        ---@param callback fun(value: any, type: string)
        ---@return fun(): nil
        once = function(self, callback) end,
        ---### Setup listeners for all fields of a table recursively
        --- This is used to ensure that listeners are notified recursively
        ---@param self ReactiveValue
        _setupAllListenersRecursively = function(self) end,
        ---### Setup listeners for a specific field of a table recursively
        --- This is used to ensure that listeners are notified recursively
        ---@param self ReactiveValue
        _setupListeners = function(self, key, value, recursive) end,
        ---### Notify listeners that the underlying value has changed
        ---@param self ReactiveValue
        ---@return nil
        ---#### Event contains:
        --- 2. value: any - The new value of the changed field
        --- 3. type: string - The type of the new value of the changed field
        _notify = function(self) end,
        ---### Notify listeners that a specific field of the underlying value has changed
        ---#### Event contains:
        --- 1. field: table - A list of keys that lead to the changed field
        --- 2. value: any - The new value of the changed field
        --- 3. type: string - The type of the new value of the changed field
        ---@param self ReactiveValue
        _notifyFieldChanged = function(self, field) end,
        ---### Notify listeners that any field of the underlying value has changed
        ---#### Event contains:
        --- 1. field: table - A list of keys that lead to the changed field
        --- 2. value: any - The new value of the changed field
        --- 3. type: string - The type of the new value of the changed field
        _notifyAnyFieldChanged = function(self, field) end,
    }
    ---### Constructor
    ---@param initialValue any
    ---@param recursive boolean?
    ---@return ReactiveValue
    ReactiveValue.new = function(initialValue, recursive)
        local self = setmetatable({}, metadata)
        self._listeners = {}
        self._fieldListeners = {}
        self._anyFieldListeners = {}
        self._oneTimeListeners = {}
        self._value = initialValue
        self._type = type(initialValue)
        self._recursive = recursive or false
        ---@return any
        self.get = function(self)
            return self._value
        end
        ---@param newValue any
        self.set = function(self, newValue)
            if self._value == newValue then
                return
            end
            if type(newValue) ~= self._type then
                error("Expected " .. self._type .. ", got " .. type(newValue))
                return
            end
            self._value = newValue
            self:_notify()
        end
        self.onChange = function(self, callback)
            if type(callback) ~= "function" then
                error("Expected function, got " .. type(callback))
                return function() end
            end
            self._listeners[callback] = true
            return function()
                self._listeners[callback] = nil
            end
        end
        self.onFieldChange = function(self, field, callback)
            if type(callback) ~= "function" then
                error("Expected function, got " .. type(callback))
                return function() end
            end
            if self._fieldListeners[field] == nil then
                self._fieldListeners[field] = {}
            end
            self._fieldListeners[field][callback] = true
            return function()
                self._fieldListeners[field][callback] = nil
            end
        end
        self.onAnyFieldChange = function(self, callback, depth)
            depth = depth or 99999
            if type(callback) ~= "function" then
                error("Expected function, got " .. type(callback))
                return function() end
            end
            if self._anyFieldListeners[depth] == nil then
                self._anyFieldListeners[depth] = {}
            end
            self._anyFieldListeners[depth][callback] = true
            return function()
                self._anyFieldListeners[depth][callback] = nil
            end
        end
        self.once = function(self, callback)
            if type(callback) ~= "function" then
                error("Expected function, got " .. type(callback))
                return function() end
            end
            self._oneTimeListeners[callback] = true
            return function()
                self._oneTimeListeners[callback] = nil
            end
        end
        self._setupAllListenersRecursively = function(self)
            if self._type ~= "table" then
                return
            end
            for key, value in pairs(self._value) do
                self:_setupListeners(key, value, true)
            end
        end
        ---@param key string
        ---@param value any
        ---@param recursive boolean?
        self._setupListeners = function(self, key, value, recursive)
            recursive = recursive or false
            if self._type ~= "table" then
                return
            end
            if getmetatable(value) ~= getmetatable(self) then
                return
            end
            value._recursive = true
            if value._type == "table" then
                value:onAnyFieldChange(function(key2)
                    ChangedKey = { key, table.unpack(key2) }
                    self:_notifyFieldChanged(ChangedKey)
                    self:_notifyAnyFieldChanged(ChangedKey)
                end)
            else
                value:onChange(function(newVal)
                    ChangedKey = { key }
                    self:_notifyFieldChanged(ChangedKey)
                    self:_notifyAnyFieldChanged(ChangedKey)
                end)
            end
            if recursive then
                value:_setupAllListenersRecursively()
            end
        end
        if recursive then
            self:_setupAllListenersRecursively()
        end
        self._notify = function(self)
            for listener, _ in pairs(self._oneTimeListeners) do
                -- task.spawn(listener, self._value, self._type)
                listener(self._value, self._type)
                self._oneTimeListeners[listener] = nil
            end
            for listener, _ in pairs(self._listeners) do
                -- task.spawn(listener, self._value, self._type)
                listener(self._value, self._type)
            end
        end
        -- TODO: Maybe implement some sort of regex here or something...
        -- Such as listening to *.field1 or something
        -- But this (having to loop over listeners and evaluate some condition) would tank performance
        -- Compared to a simple lookup
        -- So I'm not going to do anything about it for now, until I figure out a better way
        ---@param field table A list of keys that lead to the changed field
        ---@return nil
        self._notifyFieldChanged = function(self, field)
            local value = self._value
            for _, key in ipairs(field) do
                value = value[key]
            end
            local strfield = table.concat(field, ".")
            if self._fieldListeners[strfield] == nil then
                return
            end
            for listener, _ in pairs(self._fieldListeners[strfield]) do
                -- task.spawn(listener, value, type(value))
                listener(value, type(value))
            end
        end
        ---@param self ReactiveValue
        ---@param field table A list of keys that lead to the changed field
        ---@return nil
        self._notifyAnyFieldChanged = function(self, field)
            local value = self._value
            for _, key in ipairs(field) do
                value = value[key]
            end
            local keyDepth = #field
            for listenerDepth, listeners in pairs(self._anyFieldListeners) do
                if listenerDepth >= keyDepth then
                    for listener, _ in pairs(listeners) do
                        -- The reason this also returns type(value) is so that clients don't have to compute type(value)
                        -- I assume some of them might want to do it so computing it once is probably better than having every client compute it for themselves
                        -- task.spawn(listener, field, value, type(value))
                        listener(field, value, type(value))
                    end
                end
            end
        end
        self._setupComplete = true
        return self
    end
    -- S -- begintest
    -- S local invocations = 0
    -- S -- Integer example
    -- S local test = ReactiveValue.new(1)
    -- S test:onChange(function(value)
    -- S     invocations = invocations + 1
    -- S     print("test changed to " .. value)
    -- S end)
    -- S test:set(2)
    -- S assert(invocations == 1)
    -- S
    -- S invocations = 0
    -- String example
    -- S test = ReactiveValue.new("test")
    -- S test:onChange(function(value)
    -- S     invocations = invocations + 1
    -- S     print("test changed to " .. value)
    -- S end)
    -- S test:set("test2")
    -- S assert(invocations == 1)
    -- S
    -- S -- Type safety example
    -- S local res, err = pcall(test.set, test, 1)
    -- S assert(res == false)
    -- S assert(err:find("Expected string, got number"))
    -- S
    -- S -- Table example
    -- S invocations = 0
    -- S test = ReactiveValue.new({1, 2, 3})
    -- S local clbk = test:onChange(function(value)
    -- S     invocations = invocations + 1
    -- S     print("test changed to")
    -- S     dumpTable(value, 0)
    -- S end)
    -- S test:set({1, 2, 3, 4})
    -- S assert(invocations == 1)
    -- S
    -- S -- Callback removal example
    -- S clbk()
    -- S
    -- S invocations = 0
    -- S -- Any field change example
    -- S clbk = test:onAnyFieldChange(function(field, value)
    -- S     invocations = invocations + 1
    -- S     print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
    -- S end)
    -- S test.Pero = 1
    -- S test.Pero = nil
    -- S assert(invocations == 2)
    -- S clbk()
    -- S
    -- S invocations = 0
    -- S -- Field change example
    -- S test:onFieldChange("Pero", function(value)
    -- S     invocations = invocations + 1
    -- S     print("test.Pero changed to " .. value)
    -- S end)
    -- S test.Pero = 2
    -- S assert(invocations == 1)
    -- S
    -- S invocations = 0
    -- S -- One time listener example
    -- S test:once(function(value)
    -- S     invocations = invocations + 1
    -- S     print("test changed to")
    -- S     dumpTable(value, 0)
    -- S end)
    -- S test:set({3, 2, 1})
    -- S assert(invocations == 1)
    -- S
    -- S invocations = 0
    -- S -- Table push example
    -- S test = ReactiveValue.new({})
    -- S test:onChange(function(value)
    -- S     invocations = invocations + 1
    -- S     print("test changed to")
    -- S     dumpTable(value, 0)
    -- S end)
    -- S test:onAnyFieldChange(function(field, value)
    -- S     invocations = invocations + 1
    -- S     print("test." .. table.concat(field, ".") .. " changed to " .. value)
    -- S end)
    -- S test[#test + 1] = 4
    -- S assert(invocations == 2)
    -- S
    -- S invocations = 0
    -- S test = ReactiveValue.new({
    -- S     name = "pero",
    -- S     coins = ReactiveValue.new(1)
    -- S })
    -- S test.coins:onChange(function(value)
    -- S     invocations = invocations + 1
    -- S     print("test.coins changed to " .. value)
    -- S end)
    -- S test.coins:set(2)
    -- S assert(invocations == 1)
    -- S
    -- S invocations = 0
    -- S test = ReactiveValue.new({
    -- S     name = "pero",
    -- S     coins = ReactiveValue.new(1)
    -- S }, true)
    -- S test:onAnyFieldChange(function(field, value)
    -- S     invocations = invocations + 1
    -- S     print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
    -- S end)
    -- S test.coins:set(2)
    -- S test.pero2 = ReactiveValue.new({})
    -- S test.pero2.coins = ReactiveValue.new(1)
    -- S test.pero2.coins:set(2)
    -- S assert(invocations == 4)
    -- S
    -- S invocations = 0
    -- S test = ReactiveValue.new({
    -- S     name = "pero",
    -- S     coins = ReactiveValue.new({
    -- S         value = ReactiveValue.new(1)
    -- S     })
    -- S }, true)
    -- S test:onAnyFieldChange(function(field, value)
    -- S     invocations = invocations + 1
    -- S     print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
    -- S end)
    -- S test.coins.value:set(2)
    -- S assert(invocations == 1)
    -- S
    -- S invocations = 0
    -- S test = ReactiveValue.new({}, true)
    -- S test.coins = ReactiveValue.new({})
    -- S test.coins.value = ReactiveValue.new(1)
    -- S test:onAnyFieldChange(function(field, value)
    -- S     invocations = invocations + 1
    -- S     print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
    -- S end)
    -- S test.coins.value:set(3)
    -- S assert(invocations == 1)
    --S
    --S invocations = 0
    --S test = ReactiveValue.new({}, true)
    --S test:onAnyFieldChange(function(field, value)
    --S     invocations = invocations + 1
    --S     print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
    --S end, 1)
    --S test.test2 = ReactiveValue.new({}, true)
    --S test.test2.test3 = ReactiveValue.new(1)
    --S assert(invocations == 1)
    --S
    -- S -- endtest
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("GUILD_ROSTER_UPDATE")
frame:SetScript("OnEvent", function(self, event, ...)
    Init()
end)
Init()