local Grouping = {}

local MERGE_FIELDS = {
    { high = "highestCrit", old = "highestCritOld" },
    { high = "highestNormal", old = "highestNormalOld" },
    { high = "highestHealCrit", old = "highestHealCritOld" },
    { high = "highestHeal", old = "highestHealOld" },
}

local function shallowCopy(t)
    local c = {}
    for k, v in pairs(t) do
        c[k] = v
    end
    return c
end

local function maxRecord(d)
    local m = 0
    for _, f in ipairs(MERGE_FIELDS) do
        local v = d[f.high]
        if v and v > m then m = v end
    end
    return m
end

-- Allocation-free category test. This runs per record per category inside
-- broker updates and tooltip/crit-log rebuilds, so it must not build a
-- classification table per call.
function Grouping.EntryMatchesCategory(d, category)
    if type(d) ~= "table" then return false end
    if category == "pet" then
        return d.isPet and true or false
    end
    if d.isPet then return false end
    if category == "damage" then
        return ((d.highestCrit and d.highestCrit > 0)
                or (d.highestNormal and d.highestNormal > 0)) and true or false
    elseif category == "healing" then
        return ((d.highestHealCrit and d.highestHealCrit > 0)
                or (d.highestHeal and d.highestHeal > 0)) and true or false
    end
    return false
end

function Grouping.GroupAndSort(data, getName, opts)
    local byName = {}
    for spellID, rec in pairs(data or {}) do
        local name = getName(spellID, rec)
        if name then
            local group = byName[name]
            if not group then
                byName[name] = {
                    ids = { spellID },
                    data = shallowCopy(rec),
                    timestamp = rec.timestamp or 0,
                }
            else
                table.insert(group.ids, spellID)
                group.timestamp = math.max(group.timestamp or 0, rec.timestamp or 0)
                local existing = group.data
                for _, f in ipairs(MERGE_FIELDS) do
                    local incoming = rec[f.high]
                    if incoming and incoming > (existing[f.high] or 0) then
                        existing[f.old] = rec[f.old] or 0
                        existing[f.high] = incoming
                    end
                end
                if rec.isPet then existing.isPet = true end
            end
        end
    end

    local list = {}
    for name, group in pairs(byName) do
        list[#list + 1] = {
            name = name,
            data = group.data,
            ids = group.ids,
            timestamp = group.timestamp,
        }
    end

    local sortMode = opts and opts.sortMode or "recent"
    if sortMode == "highest" then
        table.sort(list, function(a, b)
            return maxRecord(a.data) > maxRecord(b.data)
        end)
    else
        table.sort(list, function(a, b)
            return (a.timestamp or 0) > (b.timestamp or 0)
        end)
    end

    return list
end

_G.CritMaticRecordGrouping = Grouping

return Grouping
