local ADDON_NAME, ns = ...
ns = ns or {}

local Util = {}
ns.Util = Util

local time = time

function Util:DeepCopy(src, dest)
    dest = dest or {}
    if type(src) ~= "table" then return src end
    for k, v in pairs(src) do
        if type(v) == "table" then
            dest[k] = self:DeepCopy(v, {})
        else
            dest[k] = v
        end
    end
    return dest
end

function Util:MergeDefaults(dst, defaults)
    if type(dst) ~= "table" then dst = {} end
    for k, v in pairs(defaults or {}) do
        if type(v) == "table" then
            dst[k] = self:MergeDefaults(dst[k], v)
        elseif dst[k] == nil then
            dst[k] = v
        end
    end
    return dst
end

function Util:Truncate(s, max)
    if not s or not max or #s <= max then return s or "" end
    return s:sub(1, max - 3) .. "..."
end

function Util:SanitizeReward(text)
    if not text then return "" end
    local s = tostring(text)
    s = s:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
    s = s:gsub("|T.-|t", "")
    s = s:gsub("^%s+", ""):gsub("%s+$", "")
    return s
end

function Util:RewardMatches(text, needles)
    local s = self:SanitizeReward(text):lower()
    if s == "" then return false end
    for _, needle in ipairs(needles or {}) do
        if needle and s:find(tostring(needle):lower(), 1, true) then
            return true
        end
    end
    return false
end

function Util:MakeTTLCache()
    local store = {}
    return {
        get = function(key)
            local e = store[key]
            if not e then return nil end
            if e.ttl and time() > e.ttl then
                store[key] = nil
                return nil
            end
            return e.value
        end,
        set = function(key, value, ttl)
            store[key] = { value = value, ttl = ttl and (time() + ttl) or nil }
        end,
        clear = function()
            store = {}
        end,
    }
end

function Util:IsDragonflightOrLater()
    local build = select(4, GetBuildInfo())
    return build and build >= 100000
end

function Util:IsRetail()
    return WOW_PROJECT_ID == WOW_PROJECT_MAINLINE
end

function Util:IsClassic()
    return WOW_PROJECT_ID == WOW_PROJECT_CLASSIC or WOW_PROJECT_ID == WOW_PROJECT_CATACLYSM_CLASSIC
end

function Util:InMistsClassic()
    local build = select(4, GetBuildInfo())
    return build and build < 60000
end

function Util:SafeCall(fn, ...)
    if type(fn) == "function" then
        return pcall(fn, ...)
    end
end

function Util:CreateEventFrame()
    local f = CreateFrame("Frame")
    f.handlers = {}
    f:SetScript("OnEvent", function(self, event, ...)
        local list = self.handlers[event]
        if list then
            for _, handler in ipairs(list) do
                handler(...)
            end
        end
    end)
    function f:On(event, handler)
        if not self.handlers[event] then self.handlers[event] = {} end
        table.insert(self.handlers[event], handler)
        self:RegisterEvent(event)
    end
    return f
end

function Util:Clamp(v, min, max)
    if v < min then return min end
    if v > max then return max end
    return v
end

function Util:Round(v, places)
    local mult = 10 ^ (places or 0)
    return math.floor((v * mult) + 0.5) / mult
end

function Util:Sort(list, comparator)
    table.sort(list, comparator)
end
