--!strict
local dom = require("../dom");
local datatypes = require("./datatypes");
local dom_datatypes = require("../datatypes");
local reflection = require("../reflection");

local presets = require("./presets");

local Signal = require("./signal");

type Signal = Signal.Signal;
type Instance = any;

local object = {};

local function isObject(value: any): boolean
    return type(value) == "table" and getmetatable(value) == object;
end
object.is = isObject;

local PROXY_MAP = newproxy(true);

getmetatable(PROXY_MAP).__index = reflection.API_CLASS_MAP;

local function blankFromClass(className: string): object
    local class = reflection:GetClass(className);
    if (not class) then
        error(`Class "{className}" not found (maybe outdated?)`);
    end
    local is_service =
        if (class.Tags) then
            table.find(class.Tags, "Service") ~= nil
        else
            false;
    return setmetatable({
        ["@instance"] = {
            className = className,
            referent = -1,
            properties = {},
            parent = nil,
            isService = is_service,
        }::dom.DOMInstance,
        ["@children"] = {},
        ["@events"] = {}::{[string]: Signal},
        ["@parent"] = nil::object?,
        ["@destroyed"] = false,
        ["@internal_map"] = PROXY_MAP,
    }, object);
end

function object.dom(dom: dom.DOMInstance): object
    local blank = blankFromClass(dom.className);
    blank["@instance"] = dom;
    return blank;
end

function object.raw(className: string): object
    return blankFromClass(className);
end

export type object = typeof(blankFromClass(""));

local TYPE_MAP = {
    ["BinaryString"] = "String",
    ["ContentId"] = "String",
    ["int64"] = "Int64",
    ["bool"] = "Bool",
    ["string"] = "String",
    ["float"] = "Float",
    ["double"] = "Double",
    ["int"] = "Int",
};

local DEFAULT_TYPE_MAP = {
    ["BinaryString"] = function(className: string, superclass: string, property: string, default: string?)
        if (default and default ~= "__api_dump_class_not_creatable__") then
            return default;
        end
        return "";
    end,
    ["ContentId"] = function(className: string, superclass: string, property: string, default: string?)
        if (default and default ~= "__api_dump_class_not_creatable__") then
            return default;
        end
        return "";
    end,
    ["int64"] = function(className: string, superclass: string, property: string, default: string?)
        return buffer.create(8);
    end,
    ["bool"] = function(className: string, superclass: string, property: string, default: string?)
        if (not default) then
            return false;
        end
        return default ~= "false";
    end,
    ["string"] = function(className: string, superclass: string, property: string, default: string?)
        if (property == "Name") then
            return className;
        end
        if (default and default ~= "__api_dump_class_not_creatable__") then
            return default;
        end
        return "";
    end,
    ["float"] = function(className: string, superclass: string, property: string, default: string?)
        if (default and default ~= "__api_dump_class_not_creatable__") then
            local num = tonumber(default);
            if (num) then
                return num;
            end
        end
        return 0;
    end,
    ["double"] = function(className: string, superclass: string, property: string, default: string?)
        if (default and default ~= "__api_dump_class_not_creatable__") then
            local num = tonumber(default);
            if (num) then
                return num;
            end
        end
        return 0;
    end,
    ["int"] = function(className: string, superclass: string, property: string, default: string?)
        if (default and default ~= "__api_dump_class_not_creatable__") then
            local num = tonumber(default);
            if (num) then
                return math.floor(num);
            end
        end
        return 0;
    end,
};

local DEFAULT_VALUE = {
    ["NumberRange"] = function(default: string?)
        if (not default) then
            return datatypes.NumberRange.new(0, 0);
        end
        local min, max = unpack(default:split(" "));
        local nmin = tonumber(min) or 0;
        return datatypes.NumberRange.new(nmin, tonumber(max) or nmin);
    end,
    ["ColorSequence"] = function(default: string?)
        if (not default) then
            return datatypes.ColorSequence.new(datatypes.Color3.new(1, 1, 1));
        end
        local points = {};
        for time, r, g, b, envople in default:gmatch("(%d+) (%d+) (%d+) (%d+) (%d+) ") do
            local time_n = tonumber(time) or 0;
            local r_n = tonumber(r) or 0;
            local g_n = tonumber(g) or 0;
            local b_n = tonumber(b) or 0;
            local envople_n = tonumber(envople) or 0;
            table.insert(points, datatypes.ColorSequenceKeypoint.new(time_n, dom_datatypes.Color3.DataType.fromRGB(r_n, g_n, b_n), envople_n));
        end
        return datatypes.ColorSequence.new(points);
    end,
    ["UDim"] = function(default: string?)
        -- if (not default) then
        -- end
        return datatypes.UDim.new(0, 0);
    end,
};

local function getSpecialDefaultPropertyHandler(className: string, name: string)
    local class = reflection:GetClass(className);
    if (not class) then
        error("Class not found");
    end
    local preset = presets[className];
    if (preset and preset.default) then
        local handler = preset.default[name];
        if (handler) then
            return handler;
        end
    end
    if (class.Superclass == "<<<ROOT>>>") then
        return;
    end
    return getSpecialDefaultPropertyHandler(class.Superclass, name);
end

local function getSpecialPropertyLoadHandler(className: string, name: string)
    local class = reflection:GetClass(className);
    if (not class) then
        error("Class not found");
    end
    local preset = presets[className];
    if (preset and preset.loader) then
        local handler = preset.loader[name];
        if (handler) then
            return handler;
        end
    end
    if (class.Superclass == "<<<ROOT>>>") then
        return;
    end
    return getSpecialPropertyLoadHandler(class.Superclass, name);
end

local IGNORE_PROPERTIES_TYPE = {
    "SecurityCapabilities",
    "PhysicalProperties",
    -- TODO: add support for these
    "Region3",
    "Region3int16",
};

local MAPPED_TYPE = {
    ["BinaryString"] = "Primitive",
    ["ContentId"] = "Primitive",
}

local function loadDefaultProperties(self: object, className: string)
    local instance = self["@instance"];
    local class = reflection:GetClass(className);
    if (not class) then
        error("Class not found");
    end
    local properties = instance.properties;
    local post_load = {};
    for _, member in class.Members do
        if (member.MemberType ~= "Property") then
            continue;
        end
        local customLoader = getSpecialPropertyLoadHandler(className, member.Name);
        if (customLoader) then
            table.insert(post_load, function(self)
                customLoader(
                    self,
                    (properties[member.Name]::any)
                );
            end);
            continue;
        end
        if (
            properties[member.Name]
            or (member.Name == "ClassName" and member.ValueType.Name == "string" and member.ValueType.Category == "Primitive")
        ) then
            continue;
        end

        local customDefault = getSpecialDefaultPropertyHandler(className, member.Name);
        if (customDefault ~= nil and type(customDefault) == "string") then
            continue;
        end

        if (
            member.Default == "__api_dump_write_only_property__"
            or (member.Tags and (
                table.find(member.Tags, "Deprecated")
                or table.find(member.Tags, "NotScriptable")
            ))
        ) then
            continue;
        end

        local default = nil;
        local serializeId = dom.roblox.PropertyTypes[member.ValueType.Name];
        local Category = MAPPED_TYPE[member.ValueType.Name] or member.ValueType.Category;
        if (Category == "Enum") then
            serializeId = dom.roblox.PropertyTypes.Enum;
            local enum = datatypes.Enums[member.ValueType.Name];
            if (enum) then
                local item = enum[member.Default];
                if (item) then
                    default = item;
                else
                    default = enum:GetEnumItems()[1];
                end
            else
                error(`Enum '{member.ValueType.Name}' not found`);
            end
        elseif (Category == "Primitive") then
            serializeId = dom.roblox.PropertyTypes[TYPE_MAP[member.ValueType.Name]];
            if (DEFAULT_TYPE_MAP[member.ValueType.Name]) then
                default = DEFAULT_TYPE_MAP[member.ValueType.Name](instance.className, className, member.Name, member.Default);
            end
        elseif (Category == "DataType") then
            local datatype = datatypes[member.ValueType.Name];
            if (datatype) then
                local custom = DEFAULT_VALUE[member.ValueType.Name];
                if (custom and member.Default ~= "__api_dump_class_not_creatable__") then
                    default = custom(member.Default);
                else
                    local ok, err = pcall(datatype.load);
                    if (ok) then
                        default = err;
                    else
                        if (not table.find(IGNORE_PROPERTIES_TYPE, member.ValueType.Name)) then
                            print(member.ValueType.Name, err);
                        end
                    end
                end
            else
                if (not table.find(IGNORE_PROPERTIES_TYPE, member.ValueType.Name)) then
                    print(`[rbx-manager] DataType '{member.ValueType.Name}' not supported`);
                end
                continue;
            end
        elseif (Category == "Class") then
            serializeId = dom.roblox.PropertyTypes.Ref;
            default = nil;
        end
        if (not serializeId) then
            continue;
        end

        if (customDefault) then
            if (type(customDefault) == "function") then
                default = customDefault(self, datatypes, default);
            end
        end

        if (default == nil) then
            continue
        end

        properties[member.Name] = {
            type = serializeId,
            value = default,
        };
    end
    if (class.Superclass ~= "<<<ROOT>>>") then
        loadDefaultProperties(self, class.Superclass);
    end
    for _, post in post_load do
        post(self);
    end
end

function object.LoadDefaultProperties(self: object, className: string)
    loadDefaultProperties(self, className);
end

function object.create(className: string): Instance
    local class = reflection:GetClass(className);
    if (not class) then
        error("Class not found");
    end

    local object = object.raw(className);

    loadDefaultProperties(object, className);

    return object::any;
end

function object.new(className: string, parent: Instance?): Instance
    local class = reflection:GetClass(className);
    if (not class) then
        error("Class not found");
    end
    if (class.Tags and table.find(class.Tags, "NotCreatable")) then
        error("Class is not creatable");
    end

    local object = object.raw(className);

    assert(parent == nil or isObject(parent), "Parent must be an instance");

    loadDefaultProperties(object, className);

    (object::any).Parent = parent;

    return object::any;
end

object.presets = presets;

local function resolveGetProperty(self: object, className: string, key: string): (boolean, any?)
    local class = reflection:GetClass(className);
    if (not class) then
        return false, nil;
    end
    local preset = presets[className];
    local handler, method;
    if (preset) then
        handler =
            if (preset.property and preset.property.get) then
                preset.property.get[key]
            else
                nil;
        method =
            if (preset.method) then
                preset.method[key]
            else
                nil;
    end
    if (handler and method) then
        error(`[object] Property and Method duplicate name conflict '{key}' in '{className}'`);
    end
    if (not handler and not method) then
        if (class.Superclass == "<<<ROOT>>>") then
            return false, nil;
        end
        local resolved, result = resolveGetProperty(self, class.Superclass, key);
        if (resolved) then
            return true, result;
        end
    end
    if (handler) then
        return true, handler(self)::any
    elseif (method) then
        assert(not self["@destroyed"], "Object is destroyed");
        return true, method;
    else
        local raw = self["@instance"].properties[key];
        if (raw) then
            return true, raw.value;
        end
        return false, nil;
    end
end

local function gettypeof(value: any)
    return if (type(value) == "table" and type(getmetatable(value)) == "table") then
        getmetatable(value).__type or type(value)
    else
        type(value);
end

local function findPropertyFromClass(class: reflection.ClassInfo, propertyName: string): reflection.PropertyMemberInfo?
    for _, member in class.Members do
        if (member.MemberType == "Property" and member.Name == propertyName) then
            return member :: reflection.PropertyMemberInfo;
        end
    end
    if (class.Superclass ~= "<<<ROOT>>>") then
        local super = assert(reflection:GetClass(class.Superclass), `Missing superclass`);
        return findPropertyFromClass(super, propertyName);
    end
    return nil;
end

local SET_PROPERTY_BUFFER = buffer.create(8);
local SET_MAP = {
    [dom.roblox.PropertyTypes.Unknown] = function(prop: dom.DOMProperty, value: any, _: string, _: string): ()
        error("Cannot set Unknown property");
    end,
    [dom.roblox.PropertyTypes.String] = function(prop: dom.DOMProperty, value: any, _: string, propertyName: string): ()
        if (type(value) ~= "string" and type(value) ~= "number") then
            error(`Unable to assign property {propertyName}. string expected, got {gettypeof(value)}`);
        end
        prop.value = tostring(value);
    end,
    [dom.roblox.PropertyTypes.Bool] = function(prop: dom.DOMProperty, value: any, _: string, _: string): ()
        prop.value = if (value) then true else false;
    end,
    [dom.roblox.PropertyTypes.Int] = function(prop: dom.DOMProperty, value: any, _: string, _: string): ()
        local num = 0
        if (type(value) ~= "number") then
            if (type(value) == "string") then
                local converted = tonumber(value);
                if (converted) then
                    num = converted;
                else
                    print(`value of type string cannot be converted to a number`);
                end
            else
                print(`value of type {gettypeof(value)} cannot be converted to a number`);
            end
        else
            num = value;
        end
        buffer.writei32(SET_PROPERTY_BUFFER, 0, num);
        prop.value = buffer.readi32(SET_PROPERTY_BUFFER, 0);
    end,
    [dom.roblox.PropertyTypes.Float] = function(prop: dom.DOMProperty, value: any, _: string, _: string): ()
        local num = 0
        if (type(value) ~= "number") then
            if (type(value) == "string") then
                local converted = tonumber(value);
                if (converted) then
                    num = converted;
                else
                    print(`value of type string cannot be converted to a number`);
                end
            else
                print(`value of type {gettypeof(value)} cannot be converted to a number`);
            end
        else
            num = value;
        end
        buffer.writef32(SET_PROPERTY_BUFFER, 0, num);
        prop.value = buffer.readf32(SET_PROPERTY_BUFFER, 0);
    end,
    [dom.roblox.PropertyTypes.Double] = function(prop: dom.DOMProperty, value: any, _: string, _: string): ()
        local num = 0
        if (type(value) ~= "number") then
            if (type(value) == "string") then
                local converted = tonumber(value);
                if (converted) then
                    num = converted;
                else
                    print(`value of type string cannot be converted to a number`);
                end
            else
                print(`value of type {gettypeof(value)} cannot be converted to a number`);
            end
        else
            num = value;
        end
        prop.value = num;
    end,
    [dom.roblox.PropertyTypes.UDim] = function(prop: dom.DOMProperty, value: any, _: string, propertyName: string): ()
        if (not dom_datatypes.UDim.Is(value)) then
            error(`Unable to assign property {propertyName}. UDim expected, got {gettypeof(value)}`);
        end
        prop.value = value;
    end,
    [dom.roblox.PropertyTypes.Enum] = function(prop: dom.DOMProperty, value: any, _: string, propertyName: string): ()
        local enumType = prop.value.EnumType;
        local new = prop.value;
        if (type(value) == "table") then
            if (type(getmetatable(value)) == "table" or getmetatable(value).__type == "EnumItem") then
                if (tostring(enumType) ~= tostring(value.EnumType)) then
                    error(`Unable to assign property {propertyName}. EnumItem of type {tostring(enumType)}, got an EnumItem of type {tostring(value.EnumType)}`);
                end
            else
                error(`Unable to assign property {propertyName}. EnumItem, number, or string expected, got {gettypeof(value)}`);
            end
        elseif (type(value) == "string") then
            local found = enumType[value];
            if (not found) then
                error(`Unable to assign property {propertyName}. Invalid value "{tostring(value)}" for enum {tostring(enumType)}`);
            end
            new = found;
        elseif (type(value) == "number") then
            local found = nil;
            for _, item in enumType:GetEnumItems() do
                if (item.Value == value) then
                    found = item;
                    break;
                end
            end
            if (not found) then
                error(`Unable to assign property {propertyName}. Invalid value {tostring(value)} for enum {tostring(enumType)}`);
            end
            new = found;
        else
            error(`Unable to assign property {propertyName}. EnumItem, number, or string expected, got {gettypeof(value)}`);
        end
        prop.value = new;
    end,
    [dom.roblox.PropertyTypes.Ref] = function(prop: dom.DOMProperty, value: any, className: string, propertyName: string): ()
        if (value ~= nil and not isObject(value)) then
            error(`Unable to assign property {propertyName}. Instance or nil expected, got {gettypeof(value)}`);
        end
        local class = reflection:GetClass(className);
        assert(class, "Class not found");
        local member = findPropertyFromClass(class, propertyName);
        assert(member, "Property not found");
        assert(member.ValueType.Category == "Class", "Property is not a reference");
        if (not value:IsA(member.ValueType.Category)) then
            error(`Expected {member.ValueType.Category} got {value.ClassName} for {className}.{propertyName}.`);
        end
        prop.value = value;
    end,
    [dom.roblox.PropertyTypes.Int64] = function(prop: dom.DOMProperty, value: any, _: string, propertyName: string): ()
        error(`Unable to assign property {propertyName}. Int64 not implemented`);
    end,
    [dom.roblox.PropertyTypes.SharedString] = function(prop: dom.DOMProperty, value: any, _: string, propertyName: string): ()
        if (type(value) ~= "string") then
            error(`Unable to assign property {propertyName}. string expected, got {gettypeof(value)}`);
        end
        prop.value = value;
    end,
    [dom.roblox.PropertyTypes.OptionalCFrame] = function(prop: dom.DOMProperty, value: any, _: string, propertyName: string): ()
        if (value ~= nil and not dom_datatypes.CFrame.Is(value)) then
            error(`Unable to assign property {propertyName}. CFrame or nil expected, got {gettypeof(value)}`);
        end
        prop.value = value;
    end,
}

local function generateSetDataType(datatype: any)
    return function(prop: dom.DOMProperty, value: any, _: string, propertyName: string): ()
        if (not datatype.Is(value)) then
            error(`Unable to assign property {propertyName}. {datatype.DataType.__type} expected, got {gettypeof(value)}`);
        end
        prop.value = value;
    end
end

SET_MAP[dom.roblox.PropertyTypes.UDim] = generateSetDataType(dom_datatypes.UDim);
SET_MAP[dom.roblox.PropertyTypes.UDim2] = generateSetDataType(dom_datatypes.UDim2);
SET_MAP[dom.roblox.PropertyTypes.Ray] = generateSetDataType(dom_datatypes.Ray);
SET_MAP[dom.roblox.PropertyTypes.Faces] = generateSetDataType(dom_datatypes.Faces);
SET_MAP[dom.roblox.PropertyTypes.Axes] = generateSetDataType(dom_datatypes.Axes);
SET_MAP[dom.roblox.PropertyTypes.BrickColor] = generateSetDataType(dom_datatypes.BrickColor);
SET_MAP[dom.roblox.PropertyTypes.Color3] = generateSetDataType(dom_datatypes.Color3);
SET_MAP[dom.roblox.PropertyTypes.Vector2] = generateSetDataType(dom_datatypes.Vector2);
SET_MAP[dom.roblox.PropertyTypes.Vector3] = generateSetDataType(dom_datatypes.Vector3);
SET_MAP[dom.roblox.PropertyTypes.CFrame] = generateSetDataType(dom_datatypes.CFrame);
SET_MAP[dom.roblox.PropertyTypes.Vector3int16] = generateSetDataType(dom_datatypes.Vector3int16);
SET_MAP[dom.roblox.PropertyTypes.NumberSequence] = generateSetDataType(dom_datatypes.NumberSequence);
SET_MAP[dom.roblox.PropertyTypes.ColorSequence] = generateSetDataType(dom_datatypes.ColorSequence);
SET_MAP[dom.roblox.PropertyTypes.NumberRange] = generateSetDataType(dom_datatypes.NumberRange);
SET_MAP[dom.roblox.PropertyTypes.Rect] = generateSetDataType(dom_datatypes.Rect);
SET_MAP[dom.roblox.PropertyTypes.PhysicalProperties] = generateSetDataType(dom_datatypes.PhysicalProperties);
SET_MAP[dom.roblox.PropertyTypes.Color3uint8] = generateSetDataType(dom_datatypes.Color3);
SET_MAP[dom.roblox.PropertyTypes.UniqueId] = generateSetDataType(dom_datatypes.UniqueId);
SET_MAP[dom.roblox.PropertyTypes.Font] = generateSetDataType(dom_datatypes.Font);
SET_MAP[dom.roblox.PropertyTypes.Content] = generateSetDataType(dom_datatypes.Content);

local function resolveSetProperty(self: object, className: string, key: string, value: any)
    if (self["@destroyed"]) then
        error("Object is destroyed");
    end
    local class = reflection:GetClass(className);
    if (not class) then
        return;
    end
    local preset = presets[className];
    local handler =
        if (preset and preset.property and preset.property.set) then
            preset.property.set[key]
        else
            nil;
    if (not handler) then
        if (class.Superclass ~= "<<<ROOT>>>") then
            if (resolveSetProperty(self, class.Superclass, key, value)) then
                return true;
            end
        end
        local prop = self["@instance"].properties[key];
        if (prop) then
            local set_handler = SET_MAP[prop.type];
            if (set_handler) then
                set_handler(prop, value, className, key);
                return;
            end
            error(`No set handler for property '{key}' on class '{className}'`);
        else
            error(`Property '{key}' does not exist on class '{className}'`);
        end
    end
    handler(self, value);
    return true;
end

local function checkRecursion(self: object, parent: object?): boolean
    local search: object? = parent;
    while search do
        if (search == self) then
            return true;
        end
        assert(search);
        search = rawget(search::any, "@parent");
    end
    return false;
end

function object.__tostring(self: object)
    local name = self["@instance"].properties["Name"];
    return `<instance {tostring(if (name) then name.value else self["@instance"].className)}>`;
end

function object.__index(self: object, key: string)
    assert(type(key) == "string", "Index must be string");
    if (key == "Parent") then
        return rawget(self::any, "@parent");
    end
    local resolved, result = resolveGetProperty(self, self["@instance"].className, key);
    if (not resolved) then
        if (self:IsA("Instance")) then
            local res = self:FindFirstChild(key);
            if (res == nil) then
                error(`{key} is not a valid member of {self:GetFullName()}`);
            end
            result = res;
        end
    end
    return result;
end

function object.__newindex(self: object, key: string, value: any)
    assert(type(key) == "string", "Index must be string");
    if (key == "Parent") then
        assert(value == nil or (isObject(value) and value:IsA("Instance")), "Parent must be an instance");
        local parent = rawget(self::any, "@parent");
        if (parent == value) then
            return;
        end
        if (checkRecursion(self, value)) then
            error("Parent cannot be a child of itself");
        end
        local descendant_added_signals = {};
        local descendant_removing_signals = {};
        local ancestry_changed_signal = self["@events"]["AncestryChanged"]::Signal?;
        local old_parent_event_signal;
        local new_parent_event_signal;
        if (parent) then
            local children_idx = table.find(parent["@children"], self);
            if (children_idx) then
                table.remove(parent["@children"], children_idx);
            else
                error("Child not found in parent (Internal Error)");
            end
            old_parent_event_signal = parent["@events"]["ChildRemoved"]::Signal?;
            while parent do
                local signal = parent["@events"]["DescendantRemoving"]::Signal?;
                if (signal) then
                    table.insert(descendant_removing_signals, signal);
                end
                parent = parent.Parent;
            end
        end
        if (value) then
            table.insert(value["@children"], self);
            new_parent_event_signal = value["@events"]["ChildAdded"]::Signal?;
            local new_parent = value;
            while new_parent do
                local signal = new_parent["@events"]["DescendantAdded"]::Signal?;
                if (signal) then
                    table.insert(descendant_added_signals, signal);
                end
                new_parent = new_parent.Parent;
            end
        end
        rawset(self::any, "@parent", value);

        if (ancestry_changed_signal) then
            ancestry_changed_signal:Fire(self, parent);
        end
        if (old_parent_event_signal) then
            old_parent_event_signal:Fire(self);
        end
        if (new_parent_event_signal) then
            new_parent_event_signal:Fire(self);
        end
        for _, signal in descendant_removing_signals do
            signal:Fire(self);
        end
        for _, signal in descendant_added_signals do
            signal:Fire(self);
        end
        return;
    end
    resolveSetProperty(self, self["@instance"].className, key, value);
end

for _, preset: any in presets do
    preset.object = object;
end

return object;


local object = require("./object");
local datatypes = require("../datatypes");
local roblox = require("../dom/roblox");
local fonts = require("./fonts");

local Enum = datatypes.Enums.DataType;

local function Alias(name: string)
    return {
        loader = function(self: object.object, from: string, property: any?)
            if (not property) then
                return;
            end
            local props = self["@instance"].properties;
            if (props[from]) then
                props[name] = props[from]
            end
        end,
        get = function(self: object.object, from: string): any
            return self[name]; -- use get handler
        end,
        set = function(self: object.object, from: string, value: any): ()
            self[name] = value; -- use set handler
            self[from] = value;
        end,
    }
end

local function Coloru8Migrate(name: string)
    return {
        loader = function(self: object.object, from: string, property: any?)
            if (not property) then
                return;
            end
            local props = self["@instance"].properties;
            if (props[from]) then
                assert(props[from].type == roblox.PropertyTypes.Color3, "Failure to migrate to Color when source is not a Color3uint8");
                if (not props[name]) then
                    props[name] = {
                        type = roblox.PropertyTypes.Color3uint8,
                    }
                end
                props[name].value = props[from].value;
                props[from] = nil;
            end
        end,
        get = function(self: object.object, from: string): any
            local Color: datatypes.Color3 = self["@instance"].properties[name].value;
            return Color;
        end,
        set = function(self: object.object, from: string, value: any): ()
            assert(datatypes.Color3.Is(value), "Cannot set non-Color3 value to a Color-migrated property");
            self[name] = value;
        end,
    }
end

local function ContentMigrate(name: string)
    return {
        loader = function(self: object.object, from: string, property: any?)
            if (not property) then
                return;
            end
            local props = self["@instance"].properties;
            if (props[from]) then
                assert(props[from].type == roblox.PropertyTypes.String, "Failure to migrate to Content when source is not a string");
                if (not props[name]) then
                    props[name] = {
                        type = roblox.PropertyTypes.Content,
                    }
                end
                props[name].value = datatypes.Content.Deserialize(Enum.ContentSourceType.Uri.Value, props[from].value, nil);
            end
        end,
        get = function(self: object.object, from: string): any
            local Content: datatypes.Content = self["@instance"].properties[name].value;
            if (Content.SourceType ~= Enum.ContentSourceType.Uri) then
                return "";
            end
            return Content.Uri;
        end,
        set = function(self: object.object, from: string, value: any): ()
            assert(type(value) == "string", "Cannot set non-string value to a Content-migrated property");
            self[name] = datatypes.Content.Deserialize(Enum.ContentSourceType.Uri.Value, value, nil);
            local props = self["@instance"].properties;
            if (not props[from]) then
                props[from] = {
                    type = roblox.PropertyTypes.String,
                }
            end
            props[from].value = value;
        end,
    }
end

local function BrickColorMigrate(name: string)
    return {
        loader = function(self: object.object, from: string, property: any?)
            if (not property) then
                return;
            end
            local props = self["@instance"].properties;
            if (props[from] and not props[name]) then
                assert(props[from].type == roblox.PropertyTypes.BrickColor, "Failure to migrate to Color when source is not a BrickColor");
                props[name] = props[from].value.Color;
                props[from] = nil;
            end
        end,
        get = function(self: object.object, from: string): any
            local Color: datatypes.Color3 = self["@instance"].properties[name].value;
            return datatypes.BrickColor.DataType.new(Color);
        end,
        set = function(self: object.object, from: string, value: any): ()
            if (not datatypes.BrickColor.Is(value)) then
                assert(type(value) == "string", "Cannot set non-BrickColor value to a Color-migrated property");
            end
            self[name] = value.Color;
        end,
    }
end

local function FontMigrate(name: string)
    return {
        loader = function(self: object.object, from: string, property: any?)
            if (not property) then
                return;
            end
            local props = self["@instance"].properties;
            if (props[from] and not props[name]) then
                assert(props[from].type == roblox.PropertyTypes.Enum, "Failure to migrate to Font when source is not a Enum");
                assert(datatypes.EnumItem.Is(props[from].value) and props[from].value.EnumType == Enum.Font, "Failure to migrate to Font when source is not a Font Enum");
                local fontface = fonts[props[from].value];
                props[name] = datatypes.Font.DataType.new(fontface.family, fontface.weight, fontface.style);
                props[from] = nil;
            end
        end,
        get = function(self: object.object, from: string): any
            -- local Font: datatypes.Font = self["@instance"].properties[name].value;
            error("Getting Font enum from migrated Front is not supported");
        end,
        set = function(self: object.object, from: string, value: any): ()
            assert(datatypes.EnumItem.Is(value) and value.EnumType == Enum.Font, "Failure to migrate to Font when source is not a Font Enum");
            local fontface = fonts[value];
            self[name] = datatypes.Font.DataType.new(fontface.family, fontface.weight, fontface.style);
        end,
    }
end

local function IgnoreGuiInsetMigrate()
    return {
        loader = function(self: object.object, from: string, property: any?)

            if (not property) then
                return;
            end
            local inset = self["@instance"].properties["ScreenInsets"];
            if (inset.value == Enum.ScreenInsets.CoreUISafeInsets) then
                inset.value = Enum.ScreenInsets.DeviceSafeInsets;
            end
        end,
        get = function(self: object.object): any
            return self["@instance"].properties["ScreenInsets"].value ~= Enum.ScreenInsets.CoreUISafeInsets
        end,
        set = function(self: object.object, from: string, value: any): ()
            local inset = self["@instance"].properties["ScreenInsets"];
            if (value) then
                if (inset.value ~= Enum.ScreenInsets.CoreUISafeInsets) then
                    return;
                end
                inset.value = Enum.ScreenInsets.DeviceSafeInsets;
            else
                inset.value = Enum.ScreenInsets.CoreUISafeInsets;
            end
        end,
    }
end

return {
    AudioPlayer = {
        Asset = ContentMigrate "AudioContent",
    },

    BackpackItem = {
        TextureId = ContentMigrate "TextureContent",
    },

    BodyAngularVelocity = {
        angularvelocity = Alias "AngularVelocity",
        maxTorque = Alias "MaxTorque",
    },
    BodyForce = {
        force = Alias "Force",
    },
    BodyGyro = {
        cframe = Alias "CFrame",
        maxTorque = Alias "MaxTorque",
    },
    BodyPosition = {
        position = Alias "Position",
        maxForce = Alias "MaxForce",
    },
    BodyThrust = {
        force = Alias "Force",
        location = Alias "Location",
    },
    BodyVelocity = {
        velocity = Alias "Velocity",
        maxForce = Alias "MaxForce",
    },

    Camera = {
        CoordinateFrame = Alias "CFrame",
        focus = Alias "Focus",
    },

    ClickDetector = {
        CursorIcon = ContentMigrate "CursorIconContent",
    },
    DragDetector = {
        ActivatedCursorIcon = ContentMigrate "ActivatedCursorIconContent",
    },

    Decal = {
        Texture = ContentMigrate "TextureContent",
        MetalnessMap = ContentMigrate "MetalnessMapContent",
        NormalMap = ContentMigrate "NormalMapContent",
        RoughnessMap = ContentMigrate "RoughnessMapContent",
    },
    ColorMap = {
        ColorMap = ContentMigrate "ColorMapContent",
    },

    ImageButton = {
        Image = ContentMigrate "ImageContent",
        HoverImage = ContentMigrate "HoverImageContent",
        PressedImage = ContentMigrate "PressedImageContent",
    },
    ImageLabel = {
        Image = ContentMigrate "ImageContent",
    },

    Instance = {
        archivable = Alias "Archivable",
    },

    JointInstance = {
        part1 = Alias "Part1",
    },

    MaterialVariant = {
        MetalnessMap = ContentMigrate "MetalnessMapContent",
        NormalMap = ContentMigrate "NormalMapContent",
        RoughnessMap = ContentMigrate "RoughnessMapContent",
        ColorMap = ContentMigrate "ColorMapContent",
    },

    Model = {
        ScaleFactor = Alias "Scale",
    },

    Mouse = {
        Icon = ContentMigrate "IconContent",
    },

    Object = {
        className = Alias "ClassName",
    },

    PackageLink = {
        PackageIdSerialized = Alias "PackageId",
    },

    BasePart = {
        brickColor = BrickColorMigrate "Color",
        BrickColor = BrickColorMigrate "Color",
        Color = Coloru8Migrate "Color3uint8",
        size = Alias "Size",
    },
    FormFactorPart = {
        formFactor = Alias "FormFactor",
        formFactorRaw = Alias "FormFactor",
    },
    Part = {
        shape = Alias "Shape",
    },
    MeshPart = {
        MeshId = ContentMigrate "MeshContent",
        TextureID = ContentMigrate "TextureContent",
    },

    Players = {
        MaxPlayersInternal = Alias "MaxPlayers",
        PreferredPlayersInternal = Alias "PreferredPlayers",
    },

    ScreenGui = {
        IgnoreGuiInset = IgnoreGuiInsetMigrate(),
    },

    ScrollingFrame = {
        BottomImage = ContentMigrate "BottomImageContent",
        MidImage = ContentMigrate "MidImageContent",
        TopImage = ContentMigrate "TopImageContent",
    },

    Sound = {
        EmitterSize = Alias "RollOffMinDistance",
        SoundId = ContentMigrate "AudioContent",
    },

    SurfaceAppearance = {
        MetalnessMap = ContentMigrate "MetalnessMapContent",
        NormalMap = ContentMigrate "NormalMapContent",
        RoughnessMap = ContentMigrate "RoughnessMapContent",
        ColorMap = ContentMigrate "ColorMapContent",
    },

    TerrainDetail = {
        MetalnessMap = ContentMigrate "MetalnessMapContent",
        NormalMap = ContentMigrate "NormalMapContent",
        RoughnessMap = ContentMigrate "RoughnessMapContent",
        ColorMap = ContentMigrate "ColorMapContent",
    },

    TextLabel = {
        Font = FontMigrate "FontFace",
    },
    TextButton = {
        Font = FontMigrate "FontFace",
    },
    TextBox = {
        Font = FontMigrate "FontFace",
    },

    UIDragDetector = {
        ActivatedCursorIcon = ContentMigrate "ActivatedCursorIconContent",
        CursorIcon = ContentMigrate "CursorIconContent",
    },

    UserInputService = {
        MouseIcon = Alias "MouseIconContent",
    },

    VideoFrame = {
        Video = ContentMigrate "VideoContent",
    },

    WeldConstraint = {
        Part0Internal = Alias "Part0",
        Part1Internal = Alias "Part1",
    },

    Workspace = {
        SignalBehavior2 = Alias "SignalBehavior",
    },

    BaseWrap = {
        CageMeshId = ContentMigrate "CageMeshContent",
    },
    WrapLayer = {
        ReferenceMeshId = ContentMigrate "ReferenceMeshContent",
    },
}


--!strict
local impl = {};
impl.object = nil::any;
impl.default = {};
impl.method = {};
impl.property = {};
impl.property.get = {};
impl.property.set = {};

impl.default["Pivot Offset"] = function(self: any, datatypes: any, default: any): any
    return datatypes.CFrame.new();
end
impl.default["BrickColor"] = function(self: any, datatypes: any, default: any): any
    return datatypes.BrickColor.new(194);
end
impl.default.PivotOffset = "DISABLED";

return impl;


--!strict
local impl = {};
impl.object = nil::any;
impl.method = {};

function impl.method.GetService(self: any, service: string): string
    return self:FindFirstChildOfClass(service);
end

function impl.method.FindService(self: any, service: string): string
    return self:FindFirstChildOfClass(service);
end

return impl;


--!strict
local Event = require("../instance/event");

local impl = {};
impl.method = {};
impl.property = {};
impl.property.get = {};
impl.property.set = {};

local function addEvent(name: string)
    impl.property.get[name] = Event.generate(name);
end

addEvent("Activated");
addEvent("MouseButton1Click");
addEvent("MouseButton1Down");
addEvent("MouseButton1Up");
addEvent("MouseButton2Click");
addEvent("MouseButton2Down");
addEvent("MouseButton2Up");

return impl;


--!strict
local Event = require("../instance/event");

local impl = {};
impl.method = {};
impl.property = {};
impl.property.get = {};
impl.property.set = {};

local function addEvent(name: string)
    impl.property.get[name] = Event.generate(name);
end

addEvent("InputBegan");
addEvent("InputChanged");
addEvent("InputEnded");
addEvent("MouseEnter");
addEvent("MouseLeave");
addEvent("MouseMoved");
addEvent("MouseWheelBackward");
addEvent("MouseWheelForward");
addEvent("SelectionGained");
addEvent("SelectionLost");
addEvent("TouchLongPress");
addEvent("TouchPan");
addEvent("TouchPinch");
addEvent("TouchRotate");
addEvent("TouchSwipe");
addEvent("TouchTap");

return impl;


--!strict
local Attributes = require("../instance/attributes");
local Event = require("../instance/event");
local Tags = require("../instance/tags");

local md5 = require("@polyfill").md5;

local impl = {};
impl.object = nil :: any;
impl.default = {};
impl.method = {};
impl.property = {};
impl.property.get = {};
impl.property.set = {};

function impl.default.Archivable(self: any): boolean
    return true;
end

function impl.method.GetFullName(self: any): string
    local name = self.Name;
    if (not name) then
        return "";
    end
    local parent = rawget(self, "@parent");
    if (not parent) then
        return name;
    end
    return `{parent:GetFullName()}.{name}`;
end

function impl.method.GetChildren(self: any): {any}
    local childrens = rawget(self, "@children") :: any;
    return table.clone(childrens);
end

function impl.method.WaitForChild(self: any, name: string): any?
    local child = self:FindFirstChild(name);
    if (child) then
        return child;
    end
    repeat
        child = self.ChildAdded:Wait();
    until (child.Name == name);
    return child;
end

function impl.method.FindFirstAncestor(self: any, name: string): any?
    local parent = rawget(self, "@parent");
    while (parent) do
        if (parent.Name == name) then
            return parent;
        end
        parent = rawget(parent, "@parent");
    end
    return;
end

function impl.method.FindFirstAncestorOfClass(self: any, className: string): any?
    local parent = rawget(self, "@parent");
    while (parent) do
        local instance = rawget(parent, "@instance") :: any;
        if (instance.className == className) then
            return parent;
        end
        parent = rawget(parent, "@parent");
    end
    return;
end

function impl.method.FindFirstAncestorWhichIsA(self: any, className: string): any?
    local parent = rawget(self, "@parent");
    while (parent) do
        if (parent:IsA(className)) then
            return parent;
        end
        parent = rawget(parent, "@parent");
    end
    return;
end

function impl.method.FindFirstChild(self: any, name: string, recursive: boolean): any?
    local childrens = rawget(self, "@children") :: any;
    for _, child in childrens do
        if (child.Name == name) then
            return child;
        end
    end
    if (recursive) then
        for _, child in childrens do
            local found = child:FindFirstChild(name, true);
            if (found ~= nil) then
                return found;
            end
        end
    end
    return nil;
end

function impl.method.FindFirstChildOfClass(self: any, className: string): any?
    local childrens = rawget(self, "@children") :: any;
    for _, child in childrens do
        local instance = rawget(child, "@instance") :: any;
        if (instance.className == className) then
            return child;
        end
    end
    return nil;
end

function impl.method.FindFirstChildWhichIsA(self: any, className: string): any?
    local childrens = rawget(self, "@children") :: any;
    for _, child in childrens do
        if (child:IsA(className)) then
            return child;
        end
    end
    return nil;
end

function impl.method.GetDescendants(self: any): {any}
    local childrens = rawget(self, "@children") :: any;
    local descendants = table.clone(childrens);
    for _, child in childrens do
        local child_descendants = child:GetDescendants()
        table.move(child_descendants, 1, #child_descendants, #descendants + 1, descendants);
    end
    return descendants;
end

function impl.method.IsDescendantOf(self: any, ancestor: any): boolean
    local parent = rawget(self, "@parent");
    while (parent) do
        if (parent == ancestor) then
            return true;
        end
        parent = rawget(parent, "@parent");
    end
    return false;
end

function impl.method.IsAncestorOf(self: any, child: any): boolean
    local parent = rawget(child, "@parent");
    while (parent) do
        if (parent == self) then
            return true;
        end
        parent = rawget(parent, "@parent");
    end
    return false;
end

function impl.method.GetActor(self: any): any
    local instance = rawget(self, "@instance") :: any;
    if (instance.className == "Actor") then
        return self;
    end
    return self:FindFirstAncestorOfClass("Actor");
end

function impl.method.GetDebugId(self: any): any
    local instance = rawget(self, "@instance") :: any;
    return md5(tostring(instance));
end

function impl.method.GetAttribute(self: any, name: string): any
    assert(type(name) == "string", "Argument #1 must be a string");
    local instance = rawget(self, "@instance") :: any;
    local attributes = instance.properties["AttributesSerialize"] or {
        type = 1,
        value = "",
    };
    local decoded = Attributes.decodeAttributes(attributes.value);
    if (not decoded[name]) then
        return nil;
    end
    return decoded[name].value;
end
function impl.method.SetAttribute(self: any, name: string, value: any): ()
    local instance = rawget(self, "@instance") :: any;
    local attributes = instance.properties["AttributesSerialize"];
    if (not attributes) then
        attributes = {
            type = 1,
            value = "",
        };
        instance.properties["AttributesSerialize"] = attributes;
    end
    if (#name > 100) then
        error(`Attribute name exceeds 100 character limit ("{name}")`);
    end
    local start = string.find(name, "[^a-zA-Z0-9%-%./_]");
    if (start) then
        error(`Attribute name contains illegal character '{string.sub(name, start, start)}' ("{name}")`);
    end
    local decoded = Attributes.decodeAttributes(attributes.value);
    decoded[name] = {
        type = Attributes.getTypeIdOf(value),
        value = value,
    };
    attributes.value = Attributes.encodeAttributes(decoded);
    local events = rawget(self, "@events") :: any;
    local event = events["AttributeChanged"];
    if (event) then
        event:Fire(name);
    end
end
function impl.method.GetAttributes(self: any, name: string, value: any): any
    local instance = rawget(self, "@instance") :: any;
    local attributes = instance.properties["AttributesSerialize"] or {
        type = 1,
        value = "",
    };
    local map = {}
    for key, data in Attributes.decodeAttributes(attributes.value) do
        map[key] = data.value;
    end
    return map;
end

function impl.method.HasTag(self: any, tag: string): any
    local instance = rawget(self, "@instance") :: any;
    local tags = instance.properties["Tags"] or {
        type = 1,
        value = "",
    };
    return table.find(Tags.decodeTags(tags.value), tag) ~= nil;
end
function impl.method.GetTags(self: any, tag: string): any
    local instance = rawget(self, "@instance") :: any;
    local tags = instance.properties["Tags"] or {
        type = 1,
        value = "",
    };
    return Tags.decodeTags(tags.value);
end
function impl.method.AddTag(self: any, tag: string): ()
    local instance = rawget(self, "@instance") :: any;
    local tags = instance.properties["Tags"] or {
        type = 1,
        value = "",
    };
    local decoded = Tags.decodeTags(tags.value);
    table.insert(decoded, tag);
    tags.value = Tags.encodeTags(decoded);
end
function impl.method.RemoveTag(self: any, tag: string): ()
    local instance = rawget(self, "@instance") :: any;
    local tags = instance.properties["Tags"] or {
        type = 1,
        value = "",
    };
    local decoded = Tags.decodeTags(tags.value);
    table.remove(decoded, table.find(decoded, tag) or -1);
    tags.value = Tags.encodeTags(decoded);
end

function impl.method.Clone(self: any): any
    local instance = rawget(self, "@instance") :: any;
    local copy = impl.object.raw(instance.className);
    local copy_instance = table.clone(instance);
    rawset(copy, "@instance", copy_instance);
    copy_instance.properties = table.clone(instance.properties);
    for property_name, property: any in copy_instance.properties do
        copy_instance.properties[property_name] = table.clone(property);
    end
    for _, child in rawget(self, "@children") :: any do
        local copy_child = child:Clone();
        local copy_child_children = rawget(copy_child, "@children") :: any;
        table.insert(copy_child_children, copy_child);
        rawset(copy_child, "@parent", copy);
    end
    return copy;
end

function impl.method.ClearAllChildren(self: any): ()
    local childs = rawget(self, "@children") :: any;
    for _, child in childs do
        rawset(child, "@parent", nil);
    end
    table.clear(childs);
end

function impl.method.Destroy(self: any): ()
    if (rawget(self, "@parent")) then
        self.Parent = nil;
    end
    rawset(self, "@destroyed", true);
    local events = rawget(self, "@events") :: any;
    --[[Destroying<Event>]] do
        local event = events["Destroying"];
        if (event) then
            event:Fire();
        end
    end
    for _, event in events do
        event:Destroy();
    end
end

function impl.property.get.Name(self: any)
    local instance = rawget(self, "@instance") :: any;
    local property = instance.properties["Name"];
    if (not property) then
        return nil;
    end
    return property.value;
end
function impl.property.get.Archivable(self: any)
    local instance = rawget(self, "@instance") :: any;
    local property = instance.properties["Archivable"];
    if (not property) then
        return nil;
    end
    return property.value;
end
function impl.property.get.className(self: any)
    local instance = rawget(self, "@instance") :: any;
    return instance.className;
end

local function addEvent(name: string)
    impl.property.get[name] = Event.generate(name);
end

addEvent("AncestryChanged");
addEvent("AttributeChanged");
addEvent("ChildAdded");
addEvent("ChildRemoved");
addEvent("DescendantAdded");
addEvent("DescendantRemoving");
addEvent("Destroying");

return impl;


--!strict
local impl = {};
impl.method = {};
impl.property = {};
impl.property.get = {};
impl.property.set = {};

function impl.method.IsA(self: any, className: string)
    local classMap = self["@internal_map"];
    local thisClassName = self["@instance"].className;
    if (thisClassName == className) then
        return true;
    end
    local class = classMap[thisClassName];
    while (class.Superclass ~= "<<<ROOT>>") do
        if (class.Superclass == className) then
            return true;
        end
        class = classMap[class.Superclass];
        if (not class) then
            break;
        end
    end
    return false;
end

function impl.property.get.ClassName(self: any)
    return self["@instance"].className;
end
function impl.property.get.className(self: any)
    return self["@instance"].className;
end

return impl;


--!strict
local Object = require("@self/Object");
local Instance = require("@self/Instance");
local BasePart = require("@self/BasePart");
local DataModel = require("@self/DataModel");
local GuiObject = require("@self/GuiObject");
local GuiButton = require("@self/GuiButton");

return {
    Object = Object,
    Instance = Instance,
    BasePart = BasePart,
    DataModel = DataModel,
    GuiObject = GuiObject,
    GuiButton = GuiButton,
}


--!strict
local task = require("@polyfill").task;

type Connection = {
    ["@next"]: Connection?,
    ["@previous"]: Connection?,
    ["@callback"]: (...any) -> (),
    Connected: boolean,
    Disconnect: (self: Connection) -> (),
};

local signal = {};
signal.__index = signal;

local function is(value: any): boolean
    return type(value) == "table" and getmetatable(value) == signal;
end

function signal.new(): Signal
    return setmetatable({
        ["@head"] = nil::Connection?,
        fire = {}::{{any}},
        queue = {}::{Connection},
        processing = false,
        firing = false,
    }, signal);
end

signal.is = is;

export type Signal = typeof(signal.new());

local function disconnect(connection: Connection)
    connection.Connected = false;
    connection["@callback"] = nil::any;
    if (connection["@previous"]) then
        connection["@previous"]["@next"] = connection["@next"];
    end
    if (connection["@next"]) then
        connection["@next"]["@previous"] = connection["@previous"];
    end
    local signal = connection["@signal"];
    if (signal["@head"] == connection) then
        signal["@head"] = connection["@next"];
    end
end

local function applyConnectionToNode(self: Signal, connection: Connection)
    local current = self["@head"];
    connection["@next"] = current;
    if (current) then
        current["@previous"] = connection;
    end
    self["@head"] = connection;
end

function signal.Connect(self: Signal, callback: (...any) -> ()): Connection
    assert(is(self), "invalid self");
    assert(type(callback) == "function", "argument #1 must be a function");
    assert(self.fire, "Signal is destroyed");
    local connection: Connection = {
        ["@signal"] = self,
        ["@callback"] = callback,
        Connected = true,
        Disconnect = disconnect,
    };
    if (self.firing) then
        table.insert(self.queue, connection);
        return connection;
    end
    applyConnectionToNode(self, connection);
    return connection;
end

function signal.Once(self: Signal, callback: (...any) -> ()): Connection
    local connection;
    connection = signal.Connect(self, function(...)
        connection:Disconnect();
        callback(...);
    end);
    return connection;
end

function signal.Wait(self: Signal): ...any
    local thread = coroutine.running();
    local connection;
    connection = signal.Connect(self, function(...)
        connection:Disconnect();
        task.spawn(thread, ...);
    end);
    return coroutine.yield();
end

local function processFireQueue(self: Signal)
    if (self.processing) then
        return;
    end
    self.processing = true;
    local queue = self.fire;
    while #queue > 0 do
        self.firing = true;
        local args = table.remove(queue, 1)::{any};
        local current = self["@head"];
        while current do
            task.spawn(current["@callback"], unpack(args));
            current = current["@next"];
        end
        self.firing = false;
        for _, connection in self.queue do
            applyConnectionToNode(self, connection);
        end
        table.clear(self.queue);
    end
    table.clear(self.fire);
    self.processing = false;
end

function signal.Fire(self: Signal, ...)
    table.insert(self.fire, {...});
    processFireQueue(self);
end

function signal.Destroy(self: Signal)
    local current = self["@head"];
    while current do
        current:Disconnect();
        current = current["@next"];
    end
    table.clear(self.fire);
    table.clear(self.queue);
    self["@head"] = nil;
    self.fire = nil::any;
end

return signal;


--!strict
local Helper = require("./Helper");
local Enums = require("./Enums");

local Axes = {};
Axes.__type = "Axes";

local isFn = Helper.DataTypeIsFn(Axes);

local AxisFlags = {
    X = 1,
    Y = 2,
    Z = 4,
}

Axes.__newindex = function()
    error("Cannot change");
end

local function notNil(value: any): boolean
    return value ~= nil;
end

local function Serialize(value: Axes): number
    local flags = 0;
    if (value.X) then
        flags = bit32.bor(flags, AxisFlags.X);
    end
    if (value.Y) then
        flags = bit32.bor(flags, AxisFlags.Y);
    end
    if (value.Z) then
        flags = bit32.bor(flags, AxisFlags.Z);
    end
    return flags;
end

local CACHE = setmetatable({}, {__mode = "v"});

local function newAxes(X: boolean, Y: boolean, Z: boolean): Axes
    local input = {
        X = X,
        Y = Y,
        Z = Z,
    };
    local id = Serialize(input)
    local cached = CACHE[id];
    if (cached) then
        return cached;
    end
    local self = setmetatable(input, Axes);
    CACHE[id] = self;
    return self::any;
end

function Axes.new(...): Axes
    local axes = {...};
    return newAxes(
        notNil(table.find(axes, Enums.DataType.NormalId.Left)
            or table.find(axes, Enums.DataType.NormalId.Right)),
        notNil(table.find(axes, Enums.DataType.NormalId.Top)
            or table.find(axes, Enums.DataType.NormalId.Bottom)),
        notNil(table.find(axes, Enums.DataType.NormalId.Front)
            or table.find(axes, Enums.DataType.NormalId.Back))
    );
end

export type Axes = typeof(Axes.new());

local function Deserialize(flags: number): Axes
    return newAxes(
        bit32.band(flags, AxisFlags.X) ~= 0,
        bit32.band(flags, AxisFlags.Y) ~= 0,
        bit32.band(flags, AxisFlags.Z) ~= 0
    )
end

return {
    DataType = Axes,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Color3 = require("./Color3");

type Color3 = Color3.Color3;

local BrickColor = {};
BrickColor.__type = "BrickColor";

local isFn = Helper.DataTypeIsFn(BrickColor);

BrickColor.__newindex = function()
    error("Cannot change");
end

type PaletteColor = {
    Name: string,
    r: number,
    g: number,
    b: number,
    Number: number,
    ArrayId: number,
}

local function palette(name: string, value: number, r: number, g: number, b: number): PaletteColor
    return {
        Name = name,
        r = r,
        g = g,
        b = b,
        Number = value,
        ArrayId = 0,
    }
end

local Palette = {
    [1] = palette("White", 1, 0.950, 0.953, 0.953),
    [2] = palette("Grey", 2, 0.632, 0.648, 0.636),
    [3] = palette("Light yellow", 3, 0.977, 0.914, 0.600),
    [5] = palette("Brick yellow", 5, 0.844, 0.773, 0.604),
    [6] = palette("Light green (Mint)", 6, 0.761, 0.855, 0.722),
    [9] = palette("Light reddish violet", 9, 0.910, 0.730, 0.785),
    [11] = palette("Pastel Blue", 11, 0.502, 0.734, 0.859),
    [12] = palette("Light orange brown", 12, 0.797, 0.518, 0.259),
    [18] = palette("Nougat", 18, 0.800, 0.557, 0.412),
    [21] = palette("Bright red", 21, 0.769, 0.157, 0.110),
    [22] = palette("Med. reddish violet", 22, 0.769, 0.440, 0.628),
    [23] = palette("Bright blue", 23, 0.051, 0.412, 0.675),
    [24] = palette("Bright yellow", 24, 0.961, 0.804, 0.189),
    [25] = palette("Earth orange", 25, 0.385, 0.279, 0.197),
    [26] = palette("Black", 26, 0.106, 0.165, 0.208),
    [27] = palette("Dark grey", 27, 0.428, 0.432, 0.424),
    [28] = palette("Dark green", 28, 0.157, 0.499, 0.279),
    [29] = palette("Medium green", 29, 0.632, 0.769, 0.550),
    [36] = palette("Lig. Yellowich orange", 36, 0.953, 0.812, 0.608),
    [37] = palette("Bright green", 37, 0.295, 0.593, 0.295),
    [38] = palette("Dark orange", 38, 0.628, 0.373, 0.208),
    [39] = palette("Light bluish violet", 39, 0.757, 0.793, 0.871),
    [40] = palette("Transparent", 40, 0.926, 0.926, 0.926),
    [41] = palette("Tr. Red", 41, 0.804, 0.330, 0.295),
    [42] = palette("Tr. Lg blue", 42, 0.757, 0.875, 0.942),
    [43] = palette("Tr. Blue", 43, 0.483, 0.714, 0.910),
    [44] = palette("Tr. Yellow", 44, 0.969, 0.946, 0.553),
    [45] = palette("Light blue", 45, 0.706, 0.824, 0.895),
    [47] = palette("Tr. Flu. Reddish orange", 47, 0.851, 0.522, 0.424),
    [48] = palette("Tr. Green", 48, 0.518, 0.714, 0.553),
    [49] = palette("Tr. Flu. Green", 49, 0.973, 0.946, 0.518),
    [50] = palette("Phosph. White", 50, 0.926, 0.910, 0.871),
    [100] = palette("Light red", 100, 0.934, 0.769, 0.714),
    [101] = palette("Medium red", 101, 0.855, 0.526, 0.479),
    [102] = palette("Medium blue", 102, 0.432, 0.600, 0.793),
    [103] = palette("Light grey", 103, 0.781, 0.757, 0.718),
    [104] = palette("Bright violet", 104, 0.420, 0.197, 0.487),
    [105] = palette("Br. yellowish orange", 105, 0.887, 0.608, 0.251),
    [106] = palette("Bright orange", 106, 0.855, 0.522, 0.255),
    [107] = palette("Bright bluish green", 107, 0.000, 0.561, 0.612),
    [108] = palette("Earth yellow", 108, 0.408, 0.361, 0.263),
    [110] = palette("Bright bluish violet", 110, 0.263, 0.330, 0.577),
    [111] = palette("Tr. Brown", 111, 0.750, 0.718, 0.695),
    [112] = palette("Medium bluish violet", 112, 0.408, 0.455, 0.675),
    [113] = palette("Tr. Medi. reddish violet", 113, 0.899, 0.679, 0.785),
    [115] = palette("Med. yellowish green", 115, 0.781, 0.824, 0.236),
    [116] = palette("Med. bluish green", 116, 0.334, 0.648, 0.687),
    [118] = palette("Light bluish green", 118, 0.718, 0.844, 0.836),
    [119] = palette("Br. yellowish green", 119, 0.644, 0.742, 0.279),
    [120] = palette("Lig. yellowish green", 120, 0.851, 0.895, 0.655),
    [121] = palette("Med. yellowish orange", 121, 0.906, 0.675, 0.346),
    [123] = palette("Br. reddish orange", 123, 0.828, 0.436, 0.299),
    [124] = palette("Bright reddish violet", 124, 0.573, 0.224, 0.471),
    [125] = palette("Light orange", 125, 0.918, 0.722, 0.573),
    [126] = palette("Tr. Bright bluish violet", 126, 0.648, 0.648, 0.797),
    [127] = palette("Gold", 127, 0.863, 0.738, 0.506),
    [128] = palette("Dark nougat", 128, 0.683, 0.479, 0.350),
    [131] = palette("Silver", 131, 0.612, 0.640, 0.659),
    [133] = palette("Neon orange", 133, 0.836, 0.451, 0.240),
    [134] = palette("Neon green", 134, 0.848, 0.867, 0.338),
    [135] = palette("Sand blue", 135, 0.455, 0.526, 0.616),
    [136] = palette("Sand violet", 136, 0.530, 0.487, 0.565),
    [137] = palette("Medium orange", 137, 0.879, 0.597, 0.393),
    [138] = palette("Sand yellow", 138, 0.585, 0.542, 0.451),
    [140] = palette("Earth blue", 140, 0.126, 0.228, 0.338),
    [141] = palette("Earth green", 141, 0.153, 0.275, 0.177),
    [143] = palette("Tr. Flu. Blue", 143, 0.812, 0.887, 0.969),
    [145] = palette("Sand blue metallic", 145, 0.475, 0.534, 0.632),
    [146] = palette("Sand violet metallic", 146, 0.585, 0.557, 0.640),
    [147] = palette("Sand yellow metallic", 147, 0.577, 0.530, 0.404),
    [148] = palette("Dark grey metallic", 148, 0.342, 0.346, 0.342),
    [149] = palette("Black metallic", 149, 0.087, 0.114, 0.197),
    [150] = palette("Light grey metallic", 150, 0.671, 0.679, 0.675),
    [151] = palette("Sand green", 151, 0.471, 0.565, 0.510),
    [153] = palette("Sand red", 153, 0.585, 0.475, 0.467),
    [154] = palette("Dark red", 154, 0.483, 0.181, 0.185),
    [157] = palette("Tr. Flu. Yellow", 157, 1.000, 0.965, 0.483),
    [158] = palette("Tr. Flu. Red", 158, 0.883, 0.644, 0.761),
    [168] = palette("Gun metallic", 168, 0.459, 0.424, 0.385),
    [176] = palette("Red flip/flop", 176, 0.593, 0.412, 0.357),
    [178] = palette("Yellow flip/flop", 178, 0.706, 0.518, 0.334),
    [179] = palette("Silver flip/flop", 179, 0.538, 0.530, 0.534),
    [180] = palette("Curry", 180, 0.844, 0.663, 0.295),
    [190] = palette("Fire Yellow", 190, 0.977, 0.840, 0.181),
    [191] = palette("Flame yellowish orange", 191, 0.910, 0.671, 0.177),
    [192] = palette("Reddish brown", 192, 0.412, 0.251, 0.157),
    [193] = palette("Flame reddish orange", 193, 0.812, 0.377, 0.142),
    [194] = palette("Medium stone grey", 194, 0.640, 0.636, 0.648),
    [195] = palette("Royal blue", 195, 0.275, 0.404, 0.644),
    [196] = palette("Dark Royal blue", 196, 0.138, 0.279, 0.546),
    [198] = palette("Bright reddish lilac", 198, 0.557, 0.259, 0.522),
    [199] = palette("Dark stone grey", 199, 0.389, 0.373, 0.385),
    [200] = palette("Lemon metalic", 200, 0.510, 0.542, 0.365),
    [208] = palette("Light stone grey", 208, 0.899, 0.895, 0.875),
    [209] = palette("Dark Curry", 209, 0.691, 0.557, 0.267),
    [210] = palette("Faded green", 210, 0.440, 0.585, 0.471),
    [211] = palette("Turquoise", 211, 0.475, 0.710, 0.710),
    [212] = palette("Light Royal blue", 212, 0.624, 0.765, 0.914),
    [213] = palette("Medium Royal blue", 213, 0.424, 0.506, 0.718),
    [216] = palette("Rust", 216, 0.565, 0.299, 0.165),
    [217] = palette("Brown", 217, 0.487, 0.361, 0.275),
    [218] = palette("Reddish lilac", 218, 0.589, 0.440, 0.624),
    [219] = palette("Lilac", 219, 0.420, 0.385, 0.608),
    [220] = palette("Light lilac", 220, 0.655, 0.663, 0.808),
    [221] = palette("Bright purple", 221, 0.804, 0.385, 0.597),
    [222] = palette("Light purple", 222, 0.895, 0.679, 0.785),
    [223] = palette("Light pink", 223, 0.863, 0.565, 0.585),
    [224] = palette("Light brick yellow", 224, 0.942, 0.836, 0.628),
    [225] = palette("Warm yellowish orange", 225, 0.922, 0.722, 0.499),
    [226] = palette("Cool yellow", 226, 0.993, 0.918, 0.553),
    [232] = palette("Dove blue", 232, 0.491, 0.734, 0.867),
    [268] = palette("Medium lilac", 268, 0.204, 0.169, 0.459),
    [301] = palette("Slime green", 301, 0.314, 0.428, 0.330),
    [302] = palette("Smoky grey", 302, 0.357, 0.365, 0.412),
    [303] = palette("Dark blue", 303, 0.000, 0.063, 0.691),
    [304] = palette("Parsley green", 304, 0.173, 0.397, 0.114),
    [305] = palette("Steel blue", 305, 0.322, 0.487, 0.683),
    [306] = palette("Storm blue", 306, 0.200, 0.346, 0.510),
    [307] = palette("Lapis", 307, 0.063, 0.165, 0.863),
    [308] = palette("Dark indigo", 308, 0.240, 0.083, 0.522),
    [309] = palette("Sea green", 309, 0.204, 0.557, 0.251),
    [310] = palette("Shamrock", 310, 0.357, 0.604, 0.299),
    [311] = palette("Fossil", 311, 0.624, 0.632, 0.675),
    [312] = palette("Mulberry", 312, 0.350, 0.134, 0.350),
    [313] = palette("Forest green", 313, 0.122, 0.502, 0.114),
    [314] = palette("Cadet blue", 314, 0.624, 0.679, 0.753),
    [315] = palette("Electric blue", 315, 0.036, 0.538, 0.812),
    [316] = palette("Eggplant", 316, 0.483, 0.000, 0.483),
    [317] = palette("Moss", 317, 0.487, 0.612, 0.420),
    [318] = palette("Artichoke", 318, 0.542, 0.671, 0.522),
    [319] = palette("Sage green", 319, 0.726, 0.769, 0.695),
    [320] = palette("Ghost grey", 320, 0.793, 0.797, 0.820),
    [321] = palette("Lilac", 321, 0.655, 0.369, 0.608),
    [322] = palette("Plum", 322, 0.483, 0.185, 0.483),
    [323] = palette("Olivine", 323, 0.581, 0.746, 0.506),
    [324] = palette("Laurel green", 324, 0.659, 0.742, 0.600),
    [325] = palette("Quill grey", 325, 0.875, 0.875, 0.871),
    [327] = palette("Crimson", 327, 0.593, 0.000, 0.000),
    [328] = palette("Mint", 328, 0.695, 0.899, 0.651),
    [329] = palette("Baby blue", 329, 0.597, 0.761, 0.859),
    [330] = palette("Carnation pink", 330, 1.000, 0.597, 0.863),
    [331] = palette("Persimmon", 331, 1.000, 0.350, 0.350),
    [332] = palette("Maroon", 332, 0.459, 0.000, 0.000),
    [333] = palette("Gold", 333, 0.938, 0.722, 0.220),
    [334] = palette("Daisy orange", 334, 0.973, 0.851, 0.428),
    [335] = palette("Pearl", 335, 0.906, 0.906, 0.926),
    [336] = palette("Fog", 336, 0.781, 0.832, 0.895),
    [337] = palette("Salmon", 337, 1.000, 0.581, 0.581),
    [338] = palette("Terra Cotta", 338, 0.746, 0.408, 0.385),
    [339] = palette("Cocoa", 339, 0.338, 0.142, 0.142),
    [340] = palette("Wheat", 340, 0.946, 0.906, 0.781),
    [341] = palette("Buttermilk", 341, 0.997, 0.953, 0.734),
    [342] = palette("Mauve", 342, 0.879, 0.699, 0.816),
    [343] = palette("Sunrise", 343, 0.832, 0.565, 0.742),
    [344] = palette("Tawny", 344, 0.589, 0.334, 0.334),
    [345] = palette("Rust", 345, 0.561, 0.299, 0.165),
    [346] = palette("Cashmere", 346, 0.828, 0.746, 0.589),
    [347] = palette("Khaki", 347, 0.887, 0.863, 0.738),
    [348] = palette("Lily white", 348, 0.930, 0.918, 0.918),
    [349] = palette("Seashell", 349, 0.914, 0.855, 0.855),
    [350] = palette("Burgundy", 350, 0.534, 0.244, 0.244),
    [351] = palette("Cork", 351, 0.738, 0.608, 0.365),
    [352] = palette("Burlap", 352, 0.781, 0.675, 0.471),
    [353] = palette("Beige", 353, 0.793, 0.750, 0.640),
    [354] = palette("Oyster", 354, 0.734, 0.702, 0.699),
    [355] = palette("Pine Cone", 355, 0.424, 0.346, 0.295),
    [356] = palette("Fawn brown", 356, 0.628, 0.518, 0.310),
    [357] = palette("Hurricane grey", 357, 0.585, 0.538, 0.534),
    [358] = palette("Cloudy grey", 358, 0.671, 0.659, 0.620),
    [359] = palette("Linen", 359, 0.687, 0.581, 0.514),
    [360] = palette("Copper", 360, 0.589, 0.404, 0.400),
    [361] = palette("Medium brown", 361, 0.338, 0.259, 0.212),
    [362] = palette("Bronze", 362, 0.495, 0.408, 0.248),
    [363] = palette("Flint", 363, 0.412, 0.400, 0.361),
    [364] = palette("Dark taupe", 364, 0.353, 0.299, 0.259),
    [365] = palette("Burnt Sienna", 365, 0.416, 0.224, 0.036),
    [1001] = palette("Institutional white", 1001, 0.973, 0.973, 0.973),
    [1002] = palette("Mid gray", 1002, 0.804, 0.804, 0.804),
    [1003] = palette("Really black", 1003, 0.067, 0.067, 0.067),
    [1004] = palette("Really red", 1004, 1.000, 0.000, 0.000),
    [1005] = palette("Deep orange", 1005, 1.000, 0.691, 0.000),
    [1006] = palette("Alder", 1006, 0.706, 0.502, 1.000),
    [1007] = palette("Dusty Rose", 1007, 0.640, 0.295, 0.295),
    [1008] = palette("Olive", 1008, 0.757, 0.746, 0.259),
    [1009] = palette("New Yeller", 1009, 1.000, 1.000, 0.000),
    [1010] = palette("Really blue", 1010, 0.000, 0.000, 1.000),
    [1011] = palette("Navy blue", 1011, 0.000, 0.126, 0.377),
    [1012] = palette("Deep blue", 1012, 0.130, 0.330, 0.726),
    [1013] = palette("Cyan", 1013, 0.016, 0.687, 0.926),
    [1014] = palette("CGA brown", 1014, 0.667, 0.334, 0.000),
    [1015] = palette("Magenta", 1015, 0.667, 0.000, 0.667),
    [1016] = palette("Pink", 1016, 1.000, 0.400, 0.800),
    [1017] = palette("Deep orange", 1017, 1.000, 0.687, 0.000),
    [1018] = palette("Teal", 1018, 0.071, 0.934, 0.832),
    [1019] = palette("Toothpaste", 1019, 0.000, 1.000, 1.000),
    [1020] = palette("Lime green", 1020, 0.000, 1.000, 0.000),
    [1021] = palette("Camo", 1021, 0.228, 0.491, 0.083),
    [1022] = palette("Grime", 1022, 0.499, 0.557, 0.393),
    [1023] = palette("Lavender", 1023, 0.550, 0.357, 0.624),
    [1024] = palette("Pastel light blue", 1024, 0.687, 0.867, 1.000),
    [1025] = palette("Pastel orange", 1025, 1.000, 0.789, 0.789),
    [1026] = palette("Pastel violet", 1026, 0.695, 0.655, 1.000),
    [1027] = palette("Pastel blue-green", 1027, 0.624, 0.953, 0.914),
    [1028] = palette("Pastel green", 1028, 0.800, 1.000, 0.800),
    [1029] = palette("Pastel yellow", 1029, 1.000, 1.000, 0.800),
    [1030] = palette("Pastel brown", 1030, 1.000, 0.800, 0.600),
    [1031] = palette("Royal purple", 1031, 0.385, 0.146, 0.820),
    [1032] = palette("Hot pink", 1032, 1.000, 0.000, 0.750),
}

local IndexesArray = table.create(208); do
    local index_count = 0;
    for i, p in Palette do
        index_count += 1;
        table.insert(IndexesArray, i);
        p.ArrayId = index_count;
    end
end

local function paletteSearch(r: number, g: number, b: number): number
    local closest_palette: PaletteColor? = nil;
    local find_vector = vector.create(r, g, b)
    for _, palette in Palette do
        if (not closest_palette) then
            closest_palette = palette;
            continue;
        end
        -- TODO: Fix types
        local palette_vector: any = vector.create(palette.r, palette.g, palette.b);
        local closest_palette_vector: any = vector.create(closest_palette.r, closest_palette.g, closest_palette.b);
        local diff_palette = vector.magnitude(find_vector - palette_vector);
        local diff_closest = vector.magnitude(find_vector - closest_palette_vector);
        if (diff_palette < diff_closest) then
            closest_palette = palette;
        end
    end
    assert(closest_palette, "Internal Palette Error");
    return closest_palette.Number;
end

function BrickColor.new(...): BrickColor
    local count = select("#", ...);
    if (count == 0) then
        return BrickColor.palette(1003);
    elseif (count == 1) then
        local value = ...;
        if (Color3.Is(value)) then
            return BrickColor.palette(paletteSearch(value.R, value.G, value.B));
        elseif (type(value) == "number") then
            return BrickColor.palette(value);
        elseif (type(value) == "string") then
            local palette = nil;
            for _, p in Palette do
                if (p.Name == value) then
                    palette = p;
                    break;
                end
            end
            assert(palette, `Unknown palette name: {value}`);
            return BrickColor.palette(palette.Number);
        else
            error("Invalid argument type: ".. type(value));
        end
    elseif (count == 3) then
        local r, g, b = ...;
        assert(type(r) == "number", `Invalid argument type: {type(r)}`);
        assert(type(g) == "number", `Invalid argument type: {type(g)}`);
        assert(type(b) == "number", `Invalid argument type: {type(b)}`);
        return BrickColor.palette(paletteSearch(r, g, b));
    end
    error("No matching 'BrickColor' constructor");
end

local CACHE = setmetatable({}, {__mode = "k"});

function BrickColor.palette(paletteValue: number): BrickColor
    local cached = CACHE[paletteValue];
    if (cached) then
        return cached;
    end
    local palette = Palette[paletteValue];
    if (not palette) then
        return BrickColor.palette(194);
    end
    local self = setmetatable({
        Name = palette.Name,
        Number = palette.Number,
        r = palette.r,
        g = palette.g,
        b = palette.b,
        Color = Color3.DataType.new(palette.r, palette.g, palette.b),
        _ArrayId = palette.ArrayId,
    }, BrickColor);
    CACHE[paletteValue] = self;
    return self;
end

function BrickColor.random(): BrickColor
    local index = math.random(1, #IndexesArray);
    return BrickColor.palette(IndexesArray[index]);
end

function BrickColor.White(): BrickColor
    return BrickColor.palette(1);
end
function BrickColor.Gray(): BrickColor
    return BrickColor.palette(194);
end
function BrickColor.DarkGray(): BrickColor
    return BrickColor.palette(199);
end
function BrickColor.Black(): BrickColor
    return BrickColor.palette(26);
end
function BrickColor.Red(): BrickColor
    return BrickColor.palette(21);
end
function BrickColor.Yellow(): BrickColor
    return BrickColor.palette(24);
end
function BrickColor.Green(): BrickColor
    return BrickColor.palette(37);
end
function BrickColor.Blue(): BrickColor
    return BrickColor.palette(23);
end

export type BrickColor = typeof(BrickColor.new());

BrickColor.__tostring = function(self: BrickColor): string
    return self.Name;
end

local function Serialize(value: BrickColor): number
    return value.Number;
end

local function Deserialize(index: number): BrickColor
    return BrickColor.palette(index);
end

return {
    DataType = BrickColor,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Vector3 = require("./Vector3");

type Vector3 = Vector3.Vector3;

local CFrame = {};
CFrame.__type = "CFrame";

local isFn = Helper.DataTypeIsFn(CFrame);

CFrame.__newindex = function()
    error("Cannot change");
end

local function checkNumbers(...)
    for _, v in {...} do
        if (type(v) ~= "number") then
            return false;
        end
    end
    return true;
end

local identity_posVector = Vector3.DataType.new(0, 0, 0);
local identity_XVector = Vector3.DataType.new(1, 0, 0);
local identity_YVector = Vector3.DataType.new(0, 1, 0);
local identity_ZVector = Vector3.DataType.new(0, 0, 1);

CFrame.identity = setmetatable({
    Position = identity_posVector,
    RightVector = identity_XVector,
    UpVector = identity_YVector,
    LookVector = -identity_ZVector,
    XVector = identity_XVector,
    YVector = identity_YVector,
    ZVector = identity_ZVector,
}, CFrame);

local CACHE = setmetatable({}, {__mode = "v"});

CACHE[Helper.ValuesHash(
    identity_posVector,
    identity_XVector,
    identity_YVector,
    identity_ZVector
)] = CFrame.identity;

function CFrame.new(...): CFrame
    local count = select("#", ...);
    if (count == 0) then
        return CFrame.identity;
    elseif (count == 1) then
        local position = ...;
        assert(Vector3.Is(position), "Invalid argument #1, expected Vector3");
        local x, y, z = position.X, position.Y, position.Z;
        local posVector = Vector3.DataType.new(x, y, z);
        local hash = Helper.ValuesHash(posVector, identity_XVector, identity_YVector, identity_ZVector);
        local cached = CACHE[hash];
        if (cached) then
            return cached;
        end
        local self = setmetatable({
            Position = posVector,
            RightVector = identity_XVector,
            UpVector = identity_YVector,
            LookVector = -identity_ZVector,
            XVector = identity_XVector,
            YVector = identity_YVector,
            ZVector = identity_ZVector,
        }, CFrame);
        CACHE[hash] = self;
        return self;
    elseif (count == 2) then
    elseif (count == 3) then
    elseif (count == 7) then
    elseif (count == 12) then
        assert(checkNumbers(...), "Invalid type (12)");
        local x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22 = ...;
        local posVector = Vector3.DataType.new(x, y, z);
        local XVector = Vector3.DataType.new(r00, r10, r20);
        local YVector = Vector3.DataType.new(r01, r11, r21);
        local ZVector = Vector3.DataType.new(r02, r12, r22);
        local hash = Helper.ValuesHash(posVector, XVector, YVector, ZVector);
        local cached = CACHE[hash];
        if (cached) then
            return cached;
        end
        local self = setmetatable({
            Position = posVector,
            RightVector = XVector,
            UpVector = YVector,
            LookVector = -ZVector,
            XVector = XVector,
            YVector = YVector,
            ZVector = ZVector,
        }, CFrame);
        CACHE[hash] = self;
        return self;
    end
    error("No matching 'CFrame' constructor");
end

export type CFrame = typeof(CFrame.new());

CFrame.__tostring = function(self: CFrame): string
    return `{self.Position.X}, {self.Position.Y}, {self.Position.Z}, `
        .. `{self.XVector.X}, {self.YVector.X}, {self.ZVector.X}, `
        .. `{self.XVector.Y}, {self.YVector.Y}, {self.ZVector.Y}, `
        .. `{self.XVector.Z}, {self.YVector.Z}, {self.ZVector.Z}`;
end

local function Serialize(value: CFrame): (
    number, number, number,
    number, number, number,
    number, number, number,
    number, number, number
)
    return value.Position.X, value.Position.Y, value.Position.Z,
        value.XVector.X, value.YVector.X, value.ZVector.X,
        value.XVector.Y, value.YVector.Y, value.ZVector.Y,
        value.XVector.Z, value.YVector.Z, value.ZVector.Z;
end

local function Deserialize(
    x: number, y: number, z: number,
    r00: number, r01: number, r02: number,
    r10: number, r11: number, r12: number,
    r20: number, r21: number, r22: number
): CFrame
    return CFrame.new(
        x, y, z,
        r00, r01, r02,
        r10, r11, r12,
        r20, r21, r22
    );
end

return {
    DataType = CFrame,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");

local Color3 = {};
Color3.__type = "Color3";

local isFn = Helper.DataTypeIsFn(Color3);

Color3.__newindex = function()
    error("Cannot change");
end

local CACHE = setmetatable({}, {__mode = "v"});

function Color3.new(red: number?, green: number?, blue: number?): Color3
    local hash = Helper.ValuesHash(
        ((red or 0) * 255) // 1,
        ((green or 0) * 255) // 1,
        ((blue or 0) * 255) // 1
    );
    local cached = CACHE[hash];
    if (cached) then
        return cached;
    end
    local self = setmetatable({
        R = red or 0,
        G = green or 0,
        B = blue or 0,
    }, Color3);
    CACHE[hash] = self;
    return self;
end

function Color3.fromRGB(red: number?, green: number?, blue: number?): Color3
    local r = (red or 0)/255;
    local g = (green or 0)/255;
    local b = (blue or 0)/255;
    return Color3.new(r, g, b);
end

function Color3.fromHSV(hue: number, saturation: number, value: number): Color3
    local chroma = value * saturation;
    local hue1 = hue / 60;
    local x = chroma * (1 - math.abs(hue1 % 2 - 1));
    local r, g, b;
    if (hue1 >= 0 and hue1 <= 1) then
        r, g, b = chroma, x, 0;
    elseif (hue1 >= 1 and hue1 <= 2) then
        r, g, b = x, chroma, 0;
    elseif (hue1 >= 2 and hue1 <= 3) then
        r, g, b = 0, chroma, x;
    elseif (hue1 >= 3 and hue1 <= 4) then
        r, g, b = 0, x, chroma;
    elseif (hue1 >= 4 and hue1 <= 5) then
        r, g, b = x, 0, chroma;
    elseif (hue1 >= 5 and hue1 <= 6) then
        r, g, b = chroma, 0, x;
    else
        r, g, b = 0, 0, 0;
    end
    local m = value - chroma;
    return Color3.new(r + m, g + m, b + m);
end

function Color3.fromHex(hex: string): Color3
    hex = hex:gsub("#", "");
    local r = tonumber(hex:sub(1, 2), 16);
    local g = tonumber(hex:sub(3, 4), 16);
    local b = tonumber(hex:sub(5, 6), 16);
    return Color3.fromRGB(r, g, b);
end

export type Color3 = typeof(Color3.new(0,0,0));

Color3.__tostring = function(self: Color3): string
    return `{self.R}, {self.G}, {self.B}`;
end

local function Serialize(value: Color3, u8: boolean): (number, number, number)
    if (u8) then
        return (value.R * 255)//1, (value.G * 255)//1, (value.B * 255)//1;
    end
    return value.R, value.G, value.B;
end

local function Deserialize(r: number, g: number, b: number, u8: boolean): Color3
    if (u8) then
        return Color3.fromRGB(r, g, b);
    end
    return Color3.new(r, g, b);
end

return {
    DataType = Color3,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Color3 = require("./Color3");
local ColorSequenceKeypoint = require("./ColorSequenceKeypoint");

type ColorSequenceKeypoint = ColorSequenceKeypoint.ColorSequenceKeypoint;

local ColorSequence = {};
ColorSequence.__type = "ColorSequence";

local isFn = Helper.DataTypeIsFn(ColorSequence);

ColorSequence.__newindex = function()
    error("Cannot change");
end

local CACHE = setmetatable({}, {__mode = "v"});

local function newColorSequence(points: {ColorSequenceKeypoint}): ColorSequence
    local hash = Helper.ValuesHash(unpack(points));
    local cached = CACHE[hash];
    if (cached) then
        return cached;
    end
    local self = setmetatable({
        Keypoints = points,
    }, ColorSequence);
    CACHE[hash] = self;
    return self;
end

function ColorSequence.new(...): ColorSequence
    local count = select("#", ...);
    if (count == 1) then
        local value = ...;
        if (Color3.Is(value)) then
            return newColorSequence({
                ColorSequenceKeypoint.DataType.new(0, value),
                ColorSequenceKeypoint.DataType.new(1, value),
            });
        elseif (type(value) == "table") then
            assert(#value >= 2, "Must have equal to or more than 2 keypoints.");
            return newColorSequence(value);
        end
    elseif (count == 2) then
        local color0, color1 = ...;
        assert(Color3.Is(color0), "Invalid argument #1, expected Color3");
        assert(Color3.Is(color1), "Invalid argument #1, expected Color3");
        return newColorSequence({
            ColorSequenceKeypoint.DataType.new(0, color0),
            ColorSequenceKeypoint.DataType.new(1, color1),
        });
    end
    error("No matching 'ColorSequence' constructor");
end

export type ColorSequence = typeof(newColorSequence({}));

ColorSequence.__tostring = function(self: ColorSequence): string
    local points = {};
    for _, point in ipairs(self.Keypoints) do
        table.insert(points, tostring(point));
    end
    return table.concat(points, " ");
end

local function Serialize(value: ColorSequence): ({ColorSequenceKeypoint})
    return value.Keypoints;
end

local function Deserialize(values: {ColorSequenceKeypoint}): ColorSequence
    return ColorSequence.new(values);
end

return {
    DataType = ColorSequence,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Color3 = require("./Color3");

type Color3 = Color3.Color3;

local ColorSequenceKeypoint = {};
ColorSequenceKeypoint.__type = "ColorSequenceKeypoint";

local isFn = Helper.DataTypeIsFn(ColorSequenceKeypoint);

ColorSequenceKeypoint.__newindex = function()
    error("Cannot change");
end

local CACHE = setmetatable({}, {__mode = "v"});

local function newColorSequenceKeypoint(time: number, color: Color3, envelope: number)
    local hash = Helper.ValuesHash(time, color, envelope);
    local cached = CACHE[hash];
    if (cached) then
        return cached;
    end
    local self = setmetatable({
        Time = time,
        Value = color,
        Envelope = envelope,
    }, ColorSequenceKeypoint);
    CACHE[hash] = self;
    return self;
end

function ColorSequenceKeypoint.new(time: number, color: Color3): ColorSequenceKeypoint
    return newColorSequenceKeypoint(time, color, 0);
end

export type ColorSequenceKeypoint = typeof(
    newColorSequenceKeypoint(0, Color3.DataType.new(0, 0, 0), 0)
);

ColorSequenceKeypoint.__tostring = function(self: ColorSequenceKeypoint): string
    return `{self.Time} {self.Value.R} {self.Value.G} {self.Value.B} {self.Envelope}`;
end

local function Serialize(value: ColorSequenceKeypoint): (number, Color3, number)
    return value.Time, value.Value, value.Envelope;
end

local function Deserialize(time: number, value: Color3, envelope: number): ColorSequenceKeypoint
    return newColorSequenceKeypoint(time, value, envelope);
end

return {
    DataType = ColorSequenceKeypoint,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Enum = require("./Enums").DataType;
local EnumItem = require("./EnumItem");

type EnumItem = EnumItem.EnumItem;

local Content = {};
Content.__type = "Content";

local isFn = Helper.DataTypeIsFn(Content);

Content.__newindex = function()
    error("Cannot change");
end

local CACHE = setmetatable({}, {__mode = "v"});

function newContent(uri: string?, object: any?, source_type: EnumItem): Content
    assert(EnumItem.Is(source_type) and source_type.EnumType == Enum.ContentSourceType, "invalid argument #3, expected Enum.ContentSourceType");
    local hash = Helper.ValuesHash(uri, object, source_type.Value);
    local cached = CACHE[hash];
    if (cached) then
        return cached;
    end
    local self = setmetatable({
        Uri = uri,
        Object = object,
        SourceType = source_type,
    }, Content);
    CACHE[hash] = self;
    return self;
end

Content.none = newContent(nil, nil, Enum.ContentSourceType.None);

Content.load = function(): Content
    return Content.none;
end

export type Content = typeof(newContent(nil, nil, Enum.ContentSourceType.None));

local function Serialize(value: Content): (number, string?, any?)
    return value.SourceType.Value, value.Uri, value.Object;
end

local function Deserialize(
    type: number,
    uri: string?,
    object: any?
): Content
    local sourceType = Enum.ContentSourceType.None;
    for _, item in Enum.ContentSourceType:GetEnumItems() do
        if (item.Value == type) then
            sourceType = item;
            break;
        end
    end
    return newContent(uri, object, sourceType);
end

return {
    DataType = Content,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");

local Enum = {};
Enum.__type = "Enum";

local isFn = Helper.DataTypeIsFn(Enum);

Enum.__newindex = function()
    error("Cannot change");
end

function Enum.new(name: string): Enum
    return setmetatable({
        ["@name"] = name,
        ["@items"] = {}::any,
    }, Enum);
end

function Enum.GetEnumItems(self: Enum)
    return table.clone(self["@items"]);
end

Enum.__index = function(self: Enum, index: any): any
    if (index == "Name") then
        return self["@name"];
    end
    for _, item in self["@items"] do
        if (type(item) == "table" and item.Name == index) then
            return item;
        end
    end
    return Enum[index];
end

Enum.__tostring = function(self: Enum): string
    return self["@name"];
end

export type Enum = typeof(Enum.new(""));

return {
    DataType = Enum,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");

type Enum = typeof(require("./Enum").DataType.new(""))

local EnumItem = {};
EnumItem.__type = "EnumItem";

local isFn = Helper.DataTypeIsFn(EnumItem);

EnumItem.__newindex = function()
    error("Cannot change");
end

function EnumItem.new(name: string, value: number, enum_type: Enum): EnumItem
    return setmetatable({
        Name = name,
        Value = value,
        EnumType = enum_type,
    }, EnumItem);
end

EnumItem.__tostring = function(self: EnumItem): string
    return `Enum.{self.EnumType.Name}.{self.Name}`;
end

export type EnumItem = typeof(EnumItem.new("", 0, (nil::any)));

return {
    DataType = EnumItem,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Enum = require("./Enum");
local EnumItem = require("./EnumItem");
local RefectionDatabase = require("../reflection");

local Enums = {};
Enums.__type = "Enums";

local isFn = Helper.DataTypeIsFn(Enums);

Enums.__newindex = function()
    error("Cannot change");
end

function Enums.new(): Enums
    local items = {};

    local dump = RefectionDatabase:GetApiDump();

    for _, enum_api in dump.Enums do
        local enum = Enum.DataType.new(enum_api.Name)
        for _, item in enum_api.Items do
            table.insert(enum["@items"], EnumItem.DataType.new(item.Name, item.Value, enum));
        end
        items[enum_api.Name] = enum;
    end

    return setmetatable({
        ["@items"] = items,
    }, Enums);
end

local function GetEnums(self: Enums)
    local array = {};
    for _, enum in self["@items"]::any do
        table.insert(array, enum);
    end
    return array;
end

Enums.__index = function(self: Enums, index: any): any
    if (index == "GetEnums") then
        return GetEnums;
    end
    return self["@items"][index];
end

export type Enums = typeof(Enums.new()) & {
    [string]: Enum.Enum,
};

return {
    DataType = Enums.new(),
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Enums = require("./Enums");

local Faces = {};
Faces.__type = "Faces";

local isFn = Helper.DataTypeIsFn(Faces);

local FaceFlags = {
    RIGHT = 1,
    TOP = 2,
    BACK = 4,
    LEFT = 8,
    BOTTOM = 16,
    FRONT = 32,
}

Faces.__newindex = function()
    error("Cannot change");
end

local function Serialize(value: Faces): number
    local flags = 0;
    if (value.Top) then
        flags = bit32.bor(flags, FaceFlags.TOP);
    end
    if (value.Bottom) then
        flags = bit32.bor(flags, FaceFlags.BOTTOM);
    end
    if (value.Left) then
        flags = bit32.bor(flags, FaceFlags.LEFT);
    end
    if (value.Right) then
        flags = bit32.bor(flags, FaceFlags.RIGHT);
    end
    if (value.Back) then
        flags = bit32.bor(flags, FaceFlags.BACK);
    end
    if (value.Front) then
        flags = bit32.bor(flags, FaceFlags.FRONT);
    end
    return flags;
end

local CACHE = setmetatable({}, {__mode = "v"});

local function newFaces(
    Top: boolean?,
    Bottom: boolean?,
    Left: boolean?,
    Right: boolean?,
    Back: boolean?,
    Front: boolean?
): Faces
    local input = {
        Top = Top or false,
        Bottom = Bottom or false,
        Left = Left or false,
        Right = Right or false,
        Back = Back or false,
        Front = Front or false,
    };
    local id = Serialize(input)
    local cached = CACHE[id];
    if (cached) then
        return cached;
    end
    local self = setmetatable(input, Faces);
    CACHE[id] = self;
    return self::any;
end

function Faces.new(...): Faces
    local normals = {...};
    return newFaces(
        table.find(normals, Enums.DataType.NormalId.Top) ~= nil,
        table.find(normals, Enums.DataType.NormalId.Bottom) ~= nil,
        table.find(normals, Enums.DataType.NormalId.Left) ~= nil,
        table.find(normals, Enums.DataType.NormalId.Right) ~= nil,
        table.find(normals, Enums.DataType.NormalId.Back) ~= nil,
        table.find(normals, Enums.DataType.NormalId.Front) ~= nil
    );
end

export type Faces = typeof(
    newFaces()
);

local function Deserialize(flags: number): Faces
    return newFaces(
        bit32.band(flags, FaceFlags.TOP) ~= 0,
        bit32.band(flags, FaceFlags.BOTTOM) ~= 0,
        bit32.band(flags, FaceFlags.LEFT) ~= 0,
        bit32.band(flags, FaceFlags.RIGHT) ~= 0,
        bit32.band(flags, FaceFlags.BACK) ~= 0,
        bit32.band(flags, FaceFlags.FRONT) ~= 0
    )
end

return {
    DataType = Faces,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local Helper = require("./Helper");
local Enum = require("./Enums").DataType;
local EnumItem = require("./EnumItem");

type EnumItem = EnumItem.EnumItem;

local Font = {};
Font.__type = "Font";

local isFn = Helper.DataTypeIsFn(Font);

Font.__newindex = function()
    error("Cannot change");
end

local CACHE = setmetatable({}, {__mode = "v"});

function Font.new(family: string, weight: EnumItem, style: EnumItem): Font
    assert(type(family) == "string", "invalid argument #1, expected string");
    assert(EnumItem.Is(weight) and weight.EnumType == Enum.FontWeight, "invalid argument #2, expected Enum.FontWeight");
    assert(EnumItem.Is(style) and style.EnumType == Enum.FontStyle, "invalid argument #3, expected Enum.FontStyle");
    local hash = Helper.ValuesHash(family, weight.Value, style.Value);
    local cached = CACHE[hash];
    if (cached) then
        return cached;
    end
    local self = setmetatable({
        Family = family,
        Weight = weight,
        Style = style,
        Bold = weight == Enum.FontWeight.Bold,
        CacheId = (nil::any)::string?,
    }, Font);
    CACHE[hash] = self;
    return self;
end

export type Font = typeof(Font.new("", Enum.FontWeight.Normal, Enum.FontStyle.Normal));

local function Serialize(value: Font): (string, number, number, string?)
    return value.Family, value.Weight.Value, value.Style.Value, value.CacheId;
end

function Font.load(family: string?, weight: EnumItem?, style: EnumItem?): Font
    local family_str = family or "rbxasset://fonts/families/SourceSansPro.json";
    local weight_enum = weight or Enum.FontWeight.Regular;
    local style_enum = style or Enum.FontStyle.Normal;
    return Font.new(family_str, weight_enum, style_enum);
end

local function Deserialize(
    family: string,
    weight: number,
    style: number,
    cacheId: string
): Font
    local weightEnum = Enum.FontWeight.Regular;
    for _, item in Enum.FontWeight:GetEnumItems() do
        if (item.Value == weight) then
            weightEnum = item;
            break;
        end
    end
    local font = Font.load(
        family,
        weightEnum,
        if (style == 0) then
            Enum.FontStyle.Normal
        else
            Enum.FontStyle.Italic
    );
    if (#cacheId > 0) then
        rawset(font::any, "CacheId", cacheId)
    end
    return font
end

return {
    DataType = Font,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};


--!strict
local md5 = require("@polyfill").md5;

local helper = {};

function helper.DataTypeIsFn(meta: any): (value: any) -> boolean
    return function(value: any)
        return
            (type(value) == "table" or type(value) == "userdata") and
            getmetatable(value) == meta;
    end;
end;

function helper.ValuesHash(...): string
    local values = {...};
    local packed = table.create(#values);
    for _, value in values do
        table.insert(packed, tostring(value));
    end
    return md5(table.concat(packed, ","));
end;

return helper;


--!strict
local Helper = require("./Helper");

local NumberRange = {};
NumberRange.__type = "NumberRange";

local isFn = Helper.DataTypeIsFn(NumberRange);

NumberRange.__newindex = function()
    error("Cannot change");
end

local CACHE = setmetatable({}, {__mode = "v"});

local function newNumberRange(min: number, max: number): NumberRange
    local hash = Helper.ValuesHash(min, max);
    local cached = CACHE[hash];
    if (cached) then
        return cached;
    end
    local self = setmetatable({
        Min = min,
        Max = max,
    }, NumberRange);
    CACHE[hash] = self;
    return self;
end

function NumberRange.new(...): NumberRange
    local count = select("#", ...);
    if (count == 1) then
        local value = ...;
        assert(type(value) == "number", "invalid argument #1, expected number");
        return newNumberRange(value, value);
    elseif (count == 2) then
        local a, b = ...;
        assert(type(a) == "number", "invalid argument #1, expected number");
        assert(type(b) == "number", "invalid argument #2, expected number");
        return newNumberRange(a, b);
    end
    error("No matching 'NumberRange' constructor");
end

export type NumberRange = typeof(NumberRange.new(0, 0));

NumberRange.__tostring = function(self: NumberRange): string
    return `{self.Min} {self.Max}`;
end

local function Serialize(value: NumberRange): (number, number)
    return value.Min, value.Max;
end

local function Deserialize(min: number, max: number): NumberRange
    return NumberRange.new(min, max);
end

return {
    DataType = NumberRange,
    Serialize = Serialize,
    Deserialize = Deserialize,
    Is = isFn,
};