return function(_, Dataset)

local sort = table.sort

function Dataset:archive()
    local table
    
    Profiler.time("Dataset:archive", function()
        table = {
            title = self:name(),
            attribution = self:attribution(),
            fields = {},
            entries = {},
        }
        
        local fieldmt = { archive = function(field) return "Field", field end }
        fieldmt.__index = fieldmt
        
        local fields = self:fields()
        local normalizedFieldNames = {}
        for index = 1, #fields do
            local field = {
                name = fields[index].name,
                default = fields[index].defaultValue,
                width = fields[index].width,
            }
            table.fields[#table.fields + 1] = setmetatable(field, fieldmt)
            normalizedFieldNames[index] = fields[index].normalizedName
        end
        sort(normalizedFieldNames)
        
        local entrymt = { archive = function(entry) return "Entry", entry[1], normalizedFieldNames end }
        entrymt.__index = entrymt
        
        for index, entry in self:entrySequence():iter() do
            table.entries[index] = setmetatable({ entry }, entrymt)
        end
    end)
    
    return "Dataset", table
end

function Dataset:entrySequence(fieldName)
    local entries = self:getEntries()
    if fieldName then
        return Sequence:new{
            getter = function(index)
                local entry = entries[index]
                return entry and entry[fieldName]
            end,
            length = function()
                return self:entryCount()
            end,
        }
    else
        return Sequence:newWithArray(entries)
    end
end

local addEntry = Dataset.add
local normalizedFieldNames = {}
local function normalize(name)
    local result = normalizedFieldNames[name]
    if not result then
        result = _G.Dataset:normalize(name)
        normalizedFieldNames[name] = result
    end
    return result
end
local function normalizeEntry(entry)
    local redo = false
    for name in pairs(entry) do
        if name ~= normalize(name) then
            redo = true
        end
    end
    local result
    if redo then
        result = {}
        for name, value in pairs(entry) do
            result[normalize(name)] = value
        end
    else
        result = entry
    end
    return result
end
function Dataset:add(entry)
    addEntry(self, normalizeEntry(entry))
end

function Dataset:memoizeField(fieldName, sequence)
    local entries = self:getEntries()
    for index = 1, #entries do
        local entry = entries[index]
        local defaults = getmetatable(entry)
        if not defaults then
            defaults = {}
            defaults.__index = defaults -- NOTE: This is safe since normalized field names are uppercase.
            setmetatable(entry, defaults)
        end
        defaults[fieldName] = sequence:getValue(index)
    end
end

function Dataset:memoize(fields)
    Profiler.time("memoize", function()
        local fieldNeedsMemoization = {}
        for index = 1, #fields do
            local field = fields[index]
            local fieldName = field.normalizedName
            fieldNeedsMemoization[fieldName] = true
        end
        repeat
            local done = true
            for index = 1, #fields do
                local field = fields[index]
                local fieldName = field.normalizedName
                if fieldNeedsMemoization[fieldName] then
                    local sequence
                    local fieldDefault = field.defaultValue
                    if Object.isa(fieldDefault, Artifact) then
                        local dependencies = fieldDefault:getDependencies()
                        local hasDependencies = true
                        for dependencyName in pairs(dependencies) do
                            hasDependencies = hasDependencies and not fieldNeedsMemoization[dependencyName]
                        end
                        if hasDependencies then
                            sequence = fieldDefault:evaluate(self)
                        end
                    elseif fieldDefault or field.memoizedDefaultValue then
                        sequence = Sequence:newWithScalar(fieldDefault)
                    else
                        fieldNeedsMemoization[fieldName] = false
                        done = false
                    end
                    if sequence then
                        local success, error = pcall(self.memoizeField, self, fieldName, sequence)
                        if not success then
                            print("Error memoizing dataset field " .. fieldName .. ":")
                            print(error)
                        end
                        fieldNeedsMemoization[fieldName] = false
                        done = false
                    end
                end
            end
        until done
        for index = 1, #fields do
            local field = fields[index]
            local fieldName = field.normalizedName
            local fieldTitle = field.name
            if fieldNeedsMemoization[fieldName] then
                warn('Could not evaluate field ' .. tostring(fieldTitle) .. ' in dataset ' .. tostring(self:name()) .. '.')
                local sequence = Sequence:newWithScalar(nil)
                self:memoizeField(fieldName, sequence)
            end
        end
    end)
end

function Dataset:heuristicFieldType(fieldName)
    fieldName = normalize(fieldName)
    local max = 0
    local maxType = 'nil'
    local typeCount = {}
    local entrySequence = self:entrySequence()
    local length = entrySequence:length()
    local steps = math.min(length, 5 + math.floor(math.sqrt(length)))
    for step = 1, steps do
        local index = 1
        if steps > 1 then
            index = 1 + math.floor((length - 1) * (step - 1) / (steps - 1))
        end
        local entry = entrySequence:getValue(index)
        if type(entry) == 'table' then
            local entryType = type(entry[fieldName])
            if entryType == 'table' or entryType == 'userdata' then
                if entry[fieldName].class then
                    entryType = entry[fieldName].class()
                else
                    entryType = 'nil'
                end
            end
            if entryType ~= 'nil' then
                typeCount[entryType] = (typeCount[entryType] or 0) + 1
                if typeCount[entryType] > max then
                    max = typeCount[entryType]
                    maxType = entryType
                end
            end
        end
    end
    return maxType
end

function Dataset:pickField(fieldType, avoidingFields)
    local avoid = {}
    if avoidingFields then
        for index = 1, #avoidingFields do
            avoid[normalize(avoidingFields[index])] = true
        end
    end
    local fields = self:fields()
    for index = 1, #fields do
        if not avoid[fields[index].normalizedName] then
            local fieldName = fields[index].name
            if self:heuristicFieldType(fieldName) == fieldType then
                return fieldName
            end
        end
    end
    for index = 1, #fields do
        if avoid[fields[index].normalizedName] then
            local fieldName = fields[index].name
            if self:heuristicFieldType(fieldName) == fieldType then
                return fieldName
            end
        end
    end
    for index = 1, #fields do
        if not avoid[fields[index].normalizedName] then
            return fields[index].name
        end
    end
    if #fields >= 1 then
        return fields[1].name
    end
end

end
