local addonName, addon = ...

addon.DB = addon.DB or {}

local DB = addon.DB

DB.VERSION = 12

DB.CATEGORIES = DB.CATEGORIES or {
    CHARACTER = "CHARACTER",
    DUNGEON = "DUNGEON",
    RAID = "RAID",
    PROFESSION = "PROFESSION",
    MOUNT = "MOUNT",
    ACHIEVEMENT = "ACHIEVEMENT",
    OTHER = "OTHER",
}

DB.CATEGORY_ORDER = {
    "OTHER",
    "CHARACTER",
    "DUNGEON",
    "RAID",
    "PROFESSION",
    "MOUNT",
    "ACHIEVEMENT",
}

local function CopyDate(dateData)
    if not dateData then
        return nil
    end

    return {
        year = tonumber(dateData.year),
        month = tonumber(dateData.month),
        day = tonumber(dateData.day),
    }
end

local function CopyTime(timeData)
    if not timeData then
        return nil
    end

    return {
        hour = tonumber(timeData.hour) or 0,
        minute = tonumber(timeData.minute) or 0,
    }
end

function DB:IsBuiltInCategory(category)
    for _, value in pairs(self.CATEGORIES) do
        if category == value then
            return true
        end
    end
    return false
end

function DB:GetCustomCategories()
    if not WhatsNextUserData then self:Initialize() end
    WhatsNextUserData.customCategories = WhatsNextUserData.customCategories or {}
    return WhatsNextUserData.customCategories
end

function DB:FindCustomCategory(categoryID)
    if not categoryID then return nil end
    for _, category in ipairs(self:GetCustomCategories()) do
        if category.id == categoryID then return category end
    end
    return nil
end

function DB:IsValidCategory(category)
    return self:IsBuiltInCategory(category)
        or self:FindCustomCategory(category) ~= nil
end

function DB:GetCategoryLabel(category)
    if category == "DUNGEON" then return "KEYS" end
    if self:IsBuiltInCategory(category) then return category or "OTHER" end
    local custom = self:FindCustomCategory(category)
    return custom and custom.name or "OTHER"
end

function DB:GetCategoryOptions()
    local result = {}
    for _, categoryID in ipairs(self.CATEGORY_ORDER) do
        table.insert(result, {
            value = categoryID,
            text = self:GetCategoryLabel(categoryID),
            custom = false,
        })
    end
    for _, category in ipairs(self:GetCustomCategories()) do
        table.insert(result, {
            value = category.id,
            text = category.name,
            custom = true,
        })
    end
    return result
end

function DB:AddCustomCategory(name)
    if not WhatsNextUserData then self:Initialize() end
    name = strtrim(tostring(name or ""))
    if name == "" then return nil, "EMPTY" end
    if #name > 30 then name = string.sub(name, 1, 30) end

    local normalized = string.lower(name)
    for _, option in ipairs(self:GetCategoryOptions()) do
        if string.lower(tostring(option.text or "")) == normalized then
            return nil, "EXISTS"
        end
    end

    local category = {
        id = "CUSTOM_" .. tostring(WhatsNextUserData.nextCustomCategoryID),
        name = name,
    }
    WhatsNextUserData.nextCustomCategoryID = WhatsNextUserData.nextCustomCategoryID + 1
    table.insert(WhatsNextUserData.customCategories, category)
    return category
end

function DB:DeleteCustomCategory(categoryID)
    if not categoryID or self:IsBuiltInCategory(categoryID) then return false end

    local list = self:GetCustomCategories()
    local found = false
    for i = #list, 1, -1 do
        if list[i].id == categoryID then
            table.remove(list, i)
            found = true
            break
        end
    end
    if not found then return false end

    for _, task in ipairs(self:GetTasks()) do
        if task.category == categoryID then
            task.category = self.CATEGORIES.OTHER
        end
    end
    return true
end

function DB:Initialize()
    WhatsNextUserData = WhatsNextUserData or {}
    WhatsNextUserData.tasks = WhatsNextUserData.tasks or {}
    WhatsNextUserData.nextTaskID = WhatsNextUserData.nextTaskID or 1
    WhatsNextUserData.nextSeriesID = WhatsNextUserData.nextSeriesID or 1
    WhatsNextUserData.nextCustomCategoryID = WhatsNextUserData.nextCustomCategoryID or 1
    WhatsNextUserData.customCategories = WhatsNextUserData.customCategories or {}
    WhatsNextUserData.settings = WhatsNextUserData.settings or {}
    WhatsNextUserData.tankAssistant = WhatsNextUserData.tankAssistant or { routes = {} }
    WhatsNextUserData.tankAssistant.routes = WhatsNextUserData.tankAssistant.routes or {}
    if WhatsNextUserData.settings.showTodayTimersWindow == nil then
        WhatsNextUserData.settings.showTodayTimersWindow = true
    end
    if WhatsNextUserData.settings.showTodayTasksWindow == nil then
        WhatsNextUserData.settings.showTodayTasksWindow = true
    end


    if WhatsNextUserData.settings.lockTodayTimersWindow == nil then
        WhatsNextUserData.settings.lockTodayTimersWindow = false
    end
    if WhatsNextUserData.settings.lockTodayTasksWindow == nil then
        WhatsNextUserData.settings.lockTodayTasksWindow = false
    end

    WhatsNextUserData.settings.todayTimersWidth = WhatsNextUserData.settings.todayTimersWidth or 330
    WhatsNextUserData.settings.todayTimersHeight = WhatsNextUserData.settings.todayTimersHeight or 190
    WhatsNextUserData.settings.todayTasksWidth = WhatsNextUserData.settings.todayTasksWidth or 380
    WhatsNextUserData.settings.todayTasksHeight = WhatsNextUserData.settings.todayTasksHeight or 300

    WhatsNextUserData.version = WhatsNextUserData.version or 1

    self:UpgradeDatabase()
end

function DB:UpgradeDatabase()
    WhatsNextUserData.tasks = WhatsNextUserData.tasks or {}
    WhatsNextUserData.nextTaskID = WhatsNextUserData.nextTaskID or 1
    WhatsNextUserData.nextSeriesID = WhatsNextUserData.nextSeriesID or 1
    WhatsNextUserData.nextCustomCategoryID = WhatsNextUserData.nextCustomCategoryID or 1
    WhatsNextUserData.customCategories = WhatsNextUserData.customCategories or {}

    local maxTaskID = 0
    local maxSeriesID = 0

    for _, task in ipairs(WhatsNextUserData.tasks) do
        if task.id == nil then
            task.id = WhatsNextUserData.nextTaskID
            WhatsNextUserData.nextTaskID = WhatsNextUserData.nextTaskID + 1
        end

        maxTaskID = math.max(maxTaskID, tonumber(task.id) or 0)

        task.title = task.title or ""
        task.completed = task.completed == true
        task.createdAt = task.createdAt or time()

        if not self:IsValidCategory(task.category) then
            task.category = self.CATEGORIES.OTHER
        end

        task.subcategory = task.subcategory or nil

        task.characterKey = task.characterKey or nil
        task.specID = task.specID or nil
        task.specName = task.specName or nil

        task.professionID = task.professionID or nil
        task.professionName = task.professionName or nil

        task.instanceID = task.instanceID or nil
        task.instanceName = task.instanceName or nil
        task.dungeonMode = task.dungeonMode or nil
        task.raidExpansionName = task.raidExpansionName or nil

        task.repeatType = task.repeatType or "none"
        task.repeatInterval = tonumber(task.repeatInterval) or 1
        task.repeatUnit = task.repeatUnit or "days"

        if task.repeatInterval < 1 then
            task.repeatInterval = 1
        end

        if task.hasTime == nil then
            task.hasTime = task.dueTime ~= nil
        end

        if not task.hasTime then
            task.dueTime = nil
        end

        if task.seriesId and not task.seriesID then
            task.seriesID = task.seriesId
            task.seriesId = nil
        end

        if task.repeatType ~= "none" and not task.seriesID then
            task.seriesID = WhatsNextUserData.nextSeriesID
            WhatsNextUserData.nextSeriesID = WhatsNextUserData.nextSeriesID + 1
        end

        if type(task.seriesID) == "number" then
            maxSeriesID = math.max(maxSeriesID, task.seriesID)
        end
    end

    if WhatsNextUserData.nextTaskID <= maxTaskID then
        WhatsNextUserData.nextTaskID = maxTaskID + 1
    end

    if WhatsNextUserData.nextSeriesID <= maxSeriesID then
        WhatsNextUserData.nextSeriesID = maxSeriesID + 1
    end

    WhatsNextUserData.version = self.VERSION
end

function DB:GetTasks()
    if not WhatsNextUserData then
        self:Initialize()
    end

    return WhatsNextUserData.tasks
end

function DB:GenerateID()
    local id = WhatsNextUserData.nextTaskID
    WhatsNextUserData.nextTaskID = id + 1
    return id
end

function DB:GenerateSeriesID()
    local id = WhatsNextUserData.nextSeriesID
    WhatsNextUserData.nextSeriesID = id + 1
    return id
end

function DB:NormalizeRepeat(repeatType, interval, unit)
    repeatType = repeatType or "none"
    interval = tonumber(interval) or 1
    unit = unit or "days"

    if interval < 1 then
        interval = 1
    end

    if repeatType == "day" then
        interval = 1
        unit = "days"
    elseif repeatType == "week" then
        interval = 1
        unit = "weeks"
    elseif repeatType == "month" then
        interval = 1
        unit = "months"
    elseif repeatType ~= "custom" then
        repeatType = "none"
        interval = 1
        unit = "days"
    end

    return repeatType, interval, unit
end

function DB:CreateTask(data)
    if not data then
        return nil
    end

    if not WhatsNextUserData then
        self:Initialize()
    end

    local title = strtrim(tostring(data.title or ""))
    if title == "" then
        return nil
    end

    local repeatType, repeatInterval, repeatUnit =
        self:NormalizeRepeat(data.repeatType, data.repeatInterval, data.repeatUnit)

    local hasTime = data.hasTime == true and data.dueDate ~= nil

    local category = data.category or self.CATEGORIES.OTHER
    if not self:IsValidCategory(category) then
        category = self.CATEGORIES.OTHER
    end

    local seriesID = data.seriesID
    if repeatType ~= "none" and not seriesID then
        seriesID = self:GenerateSeriesID()
    end

    local task = {
        id = self:GenerateID(),
        title = title,
        createdAt = time(),
        completed = false,

        category = category,
        subcategory = data.subcategory or nil,

        characterKey = data.characterKey or nil,
        specID = data.specID or nil,
        specName = data.specName or nil,

        professionID = data.professionID or nil,
        professionName = data.professionName or nil,

        instanceID = data.instanceID or nil,
        instanceName = data.instanceName or nil,
        dungeonMode = data.dungeonMode or nil,
        raidExpansionName = data.raidExpansionName or nil,

        dueDate = CopyDate(data.dueDate),

        hasTime = hasTime,
        dueTime = hasTime and CopyTime(data.dueTime) or nil,

        repeatType = repeatType,
        repeatInterval = repeatInterval,
        repeatUnit = repeatUnit,
        seriesID = seriesID,

        generatedFromID = data.generatedFromID or nil,
    }

    table.insert(WhatsNextUserData.tasks, task)

    if task.repeatType ~= "none" and task.dueDate and data.skipFutureGeneration ~= true then
        self:EnsureSeriesThroughDate(
            task.seriesID,
            self:GetDateAfterDays(task.dueDate, 90)
        )
    end

    return task
end


local function ApplyEditableData(DBObject, task, data)
    if not task or not data then return false end

    local title = strtrim(tostring(data.title or ""))
    if title == "" then return false end

    local repeatType, repeatInterval, repeatUnit =
        DBObject:NormalizeRepeat(data.repeatType, data.repeatInterval, data.repeatUnit)

    local category = data.category or DBObject.CATEGORIES.OTHER
    if not DBObject:IsValidCategory(category) then
        category = DBObject.CATEGORIES.OTHER
    end

    local dueDate = CopyDate(data.dueDate)
    local hasTime = data.hasTime == true and dueDate ~= nil

    task.title = title
    task.category = category
    task.subcategory = data.subcategory or nil

    task.characterKey = data.characterKey or nil
    task.specID = data.specID or nil
    task.specName = data.specName or nil

    task.professionID = data.professionID or nil
    task.professionName = data.professionName or nil

    task.instanceID = data.instanceID or nil
    task.instanceName = data.instanceName or nil
    task.dungeonMode = data.dungeonMode or nil
    task.raidExpansionName = data.raidExpansionName or nil

    task.dueDate = dueDate
    task.hasTime = hasTime
    task.dueTime = hasTime and CopyTime(data.dueTime) or nil

    task.repeatType = repeatType
    task.repeatInterval = repeatInterval
    task.repeatUnit = repeatUnit

    return true
end

function DB:UpdateTask(id, data)
    local task = self:FindTask(id)
    if not task then return nil end

    local oldSeriesID = task.seriesID
    if not ApplyEditableData(self, task, data) then return nil end

    if task.repeatType ~= "none" and not task.seriesID then
        task.seriesID = self:GenerateSeriesID()
    elseif task.repeatType == "none" then
        -- This one can be an exception while the rest of the series survives.
        task.seriesID = oldSeriesID
    end

    return task
end

function DB:UpdateTaskSeries(id, data)
    local selected = self:FindTask(id)
    if not selected then return nil end
    if not selected.seriesID then return self:UpdateTask(id, data) end

    local seriesID = selected.seriesID
    local oldDate = CopyDate(selected.dueDate)
    local repeatType = self:NormalizeRepeat(
        data.repeatType, data.repeatInterval, data.repeatUnit
    )

    local tasks = self:GetTasks()

    -- Turning repeat off for ALL leaves one edited task and removes the series.
    if repeatType == "none" then
        for i = #tasks, 1, -1 do
            if tasks[i].seriesID == seriesID and tasks[i].id ~= id then
                table.remove(tasks, i)
            end
        end
        local current = self:FindTask(id)
        if not current or not ApplyEditableData(self, current, data) then return nil end
        current.seriesID = nil
        current.generatedFromID = nil
        return current
    end

    -- Update previous occurrences' shared fields but preserve dates/status.
    for _, task in ipairs(tasks) do
        if task.seriesID == seriesID then
            local earlier = false
            if oldDate and task.dueDate then
                earlier = self:CompareDates(task.dueDate, oldDate) < 0
            end
            if earlier then
                local keepDate = CopyDate(task.dueDate)
                local keepCompleted = task.completed
                ApplyEditableData(self, task, data)
                task.dueDate = keepDate
                task.completed = keepCompleted
                task.seriesID = seriesID
            end
        end
    end

    -- Remove selected and future occurrences, then regenerate from edited one.
    for i = #tasks, 1, -1 do
        local task = tasks[i]
        if task.seriesID == seriesID then
            local remove = task.id == id
            if not remove and oldDate and task.dueDate then
                remove = self:CompareDates(task.dueDate, oldDate) >= 0
            end
            if remove then table.remove(tasks, i) end
        end
    end

    return self:CreateTask({
        title = data.title,
        dueDate = data.dueDate,
        hasTime = data.hasTime,
        dueTime = data.dueTime,
        repeatType = data.repeatType,
        repeatInterval = data.repeatInterval,
        repeatUnit = data.repeatUnit,
        category = data.category,
        subcategory = data.subcategory,
        characterKey = data.characterKey,
        specID = data.specID,
        specName = data.specName,
        professionID = data.professionID,
        professionName = data.professionName,
        instanceID = data.instanceID,
        instanceName = data.instanceName,
        dungeonMode = data.dungeonMode,
        raidExpansionName = data.raidExpansionName,
        seriesID = seriesID,
    })
end


function DB:FindTask(id)
    for _, task in ipairs(self:GetTasks()) do
        if task.id == id then
            return task
        end
    end

    return nil
end

function DB:DeleteTask(id)
    local tasks = self:GetTasks()

    for index, task in ipairs(tasks) do
        if task.id == id then
            table.remove(tasks, index)
            return true
        end
    end

    return false
end

function DB:DeleteTaskSeries(seriesIDOrTaskID)
    local seriesID = seriesIDOrTaskID

    local task = self:FindTask(seriesIDOrTaskID)
    if task then
        seriesID = task.seriesID
    end

    if not seriesID then
        return false
    end

    local removed = false
    local tasks = self:GetTasks()

    for index = #tasks, 1, -1 do
        if tasks[index].seriesID == seriesID then
            table.remove(tasks, index)
            removed = true
        end
    end

    return removed
end

function DB:IsRepeatingTask(task)
    return task ~= nil
        and task.repeatType ~= nil
        and task.repeatType ~= "none"
end

function DB:CompleteTask(id)
    local task = self:FindTask(id)
    if not task then
        return
    end

    task.completed = true

    if self:IsRepeatingTask(task) and task.seriesID and task.dueDate then
        self:EnsureSeriesThroughDate(
            task.seriesID,
            self:GetDateAfterDays(task.dueDate, 90)
        )
    end
end

function DB:UncompleteTask(id)
    local task = self:FindTask(id)
    if task then
        task.completed = false
    end
end

function DB:DateToTimestamp(dateData, timeData)
    if not dateData then
        return nil
    end

    return time({
        year = tonumber(dateData.year),
        month = tonumber(dateData.month),
        day = tonumber(dateData.day),
        hour = timeData and tonumber(timeData.hour) or 12,
        min = timeData and tonumber(timeData.minute) or 0,
        sec = 0,
    })
end

function DB:GetDateAfterDays(dateData, days)
    if not dateData then
        return nil
    end

    local timestamp = self:DateToTimestamp(dateData) + ((tonumber(days) or 0) * 86400)
    local result = date("*t", timestamp)

    return {
        year = result.year,
        month = result.month,
        day = result.day,
    }
end

function DB:CompareDates(a, b)
    if not a and not b then
        return 0
    elseif not a then
        return -1
    elseif not b then
        return 1
    end

    local at = self:DateToTimestamp(a)
    local bt = self:DateToTimestamp(b)

    if at < bt then
        return -1
    elseif at > bt then
        return 1
    end

    return 0
end

function DB:SameDate(a, b)
    return a
        and b
        and tonumber(a.year) == tonumber(b.year)
        and tonumber(a.month) == tonumber(b.month)
        and tonumber(a.day) == tonumber(b.day)
end

function DB:GetNextDate(dateData, interval, unit)
    if not dateData then
        return nil
    end

    interval = tonumber(interval) or 1
    if interval < 1 then
        interval = 1
    end

    unit = unit or "days"

    if unit == "days" or unit == "weeks" then
        local days = interval
        if unit == "weeks" then
            days = interval * 7
        end

        return self:GetDateAfterDays(dateData, days)
    end

    if unit == "months" then
        local sourceDay = tonumber(dateData.day) or 1

        local target = {
            year = tonumber(dateData.year),
            month = tonumber(dateData.month) + interval,
            day = 1,
            hour = 12,
        }

        local normalized = date("*t", time(target))

        local followingMonth = {
            year = normalized.year,
            month = normalized.month + 1,
            day = 1,
            hour = 12,
        }

        local followingTimestamp = time(followingMonth)
        local lastDay = tonumber(date("%d", followingTimestamp - 86400))

        return {
            year = normalized.year,
            month = normalized.month,
            day = math.min(sourceDay, lastDay),
        }
    end

    return nil
end

function DB:GetSeriesTasks(seriesID)
    local result = {}

    for _, task in ipairs(self:GetTasks()) do
        if task.seriesID == seriesID then
            table.insert(result, task)
        end
    end

    table.sort(result, function(a, b)
        return self:CompareDates(a.dueDate, b.dueDate) < 0
    end)

    return result
end

function DB:FindSeriesTaskForDate(seriesID, dateData)
    for _, task in ipairs(self:GetTasks()) do
        if task.seriesID == seriesID and self:SameDate(task.dueDate, dateData) then
            return task
        end
    end

    return nil
end

function DB:GetSeriesTemplate(seriesID)
    local tasks = self:GetSeriesTasks(seriesID)
    return tasks[1]
end

function DB:EnsureSeriesThroughDate(seriesID, throughDate)
    if not seriesID or not throughDate then
        return
    end

    local seriesTasks = self:GetSeriesTasks(seriesID)
    if #seriesTasks == 0 then
        return
    end

    local template = seriesTasks[1]
    local lastTask = seriesTasks[#seriesTasks]

    if not template.dueDate or template.repeatType == "none" then
        return
    end

    local safety = 0

    while self:CompareDates(lastTask.dueDate, throughDate) < 0 do
        safety = safety + 1
        if safety > 500 then
            break
        end

        local nextDate = self:GetNextDate(
            lastTask.dueDate,
            template.repeatInterval,
            template.repeatUnit
        )

        if not nextDate then
            break
        end

        local existing = self:FindSeriesTaskForDate(seriesID, nextDate)

        if existing then
            lastTask = existing
        else
            local newTask = self:CreateTask({
                title = template.title,
                dueDate = nextDate,
                hasTime = template.hasTime,
                dueTime = template.dueTime,
                repeatType = template.repeatType,
                repeatInterval = template.repeatInterval,
                repeatUnit = template.repeatUnit,
                category = template.category,
                subcategory = template.subcategory,

                characterKey = template.characterKey,
                specID = template.specID,
                specName = template.specName,

                professionID = template.professionID,
                professionName = template.professionName,

                instanceID = template.instanceID,
                instanceName = template.instanceName,
                dungeonMode = template.dungeonMode,
                raidExpansionName = template.raidExpansionName,

                seriesID = seriesID,
                generatedFromID = lastTask.id,
                skipFutureGeneration = true,
            })

            if not newTask then
                break
            end

            lastTask = newTask
        end
    end
end

function DB:EnsureAllSeriesThroughDate(throughDate)
    local seen = {}

    for _, task in ipairs(self:GetTasks()) do
        if task.seriesID and task.repeatType ~= "none" and not seen[task.seriesID] then
            seen[task.seriesID] = true
            self:EnsureSeriesThroughDate(task.seriesID, throughDate)
        end
    end
end

function DB:GetTaskTimestamp(task)
    if not task or not task.dueDate then
        return nil
    end

    return self:DateToTimestamp(
        task.dueDate,
        self:TaskHasTime(task) and task.dueTime or nil
    )
end

function DB:TaskHasTime(task)
    return task ~= nil
        and task.hasTime == true
        and task.dueTime ~= nil
end

function DB:IsTaskOverdue(task)
    if not self:TaskHasTime(task) or task.completed then
        return false
    end

    local timestamp = self:GetTaskTimestamp(task)
    return timestamp ~= nil and timestamp < time()
end

function DB:GetRemainingSeconds(task)
    if not self:TaskHasTime(task) then
        return nil
    end

    local timestamp = self:GetTaskTimestamp(task)
    return timestamp and (timestamp - time()) or nil
end

function DB:GetDateKey(task)
    if not task or not task.dueDate then
        return "NO_DATE"
    end

    return string.format(
        "%04d-%02d-%02d",
        task.dueDate.year,
        task.dueDate.month,
        task.dueDate.day
    )
end

function DB:GetUndatedTasks()
    local result = {}

    for _, task in ipairs(self:GetTasks()) do
        if not task.dueDate then
            table.insert(result, task)
        end
    end

    return result
end

function DB:GetTasksForDate(year, month, day)
    local result = {}

    for _, task in ipairs(self:GetTasks()) do
        if task.dueDate
            and task.dueDate.year == year
            and task.dueDate.month == month
            and task.dueDate.day == day
        then
            table.insert(result, task)
        end
    end

    table.sort(result, function(a, b)
        local at = self:GetTaskTimestamp(a) or 9999999999
        local bt = self:GetTaskTimestamp(b) or 9999999999

        if at == bt then
            return (a.createdAt or 0) < (b.createdAt or 0)
        end

        return at < bt
    end)

    return result
end

function DB:GetAllTasks()
    local result = {}

    for _, task in ipairs(self:GetTasks()) do
        table.insert(result, task)
    end

    table.sort(result, function(a, b)
        if not a.dueDate and b.dueDate then
            return false
        end

        if a.dueDate and not b.dueDate then
            return true
        end

        if not a.dueDate and not b.dueDate then
            return (a.createdAt or 0) < (b.createdAt or 0)
        end

        local at = self:GetTaskTimestamp(a) or 9999999999
        local bt = self:GetTaskTimestamp(b) or 9999999999

        if at == bt then
            return (a.createdAt or 0) < (b.createdAt or 0)
        end

        return at < bt
    end)

    return result
end

function DB:GetUpcomingTimedTasks(limit)
    limit = tonumber(limit) or 5

    local now = time()
    local result = {}

    for _, task in ipairs(self:GetTasks()) do
        if not task.completed and self:TaskHasTime(task) then
            local timestamp = self:GetTaskTimestamp(task)

            if timestamp and timestamp >= now then
                table.insert(result, task)
            end
        end
    end

    table.sort(result, function(a, b)
        return self:GetTaskTimestamp(a) < self:GetTaskTimestamp(b)
    end)

    local limited = {}

    for index = 1, math.min(#result, limit) do
        table.insert(limited, result[index])
    end

    return limited
end
