local _, ns = ...

ns.Utils = {}
local Utils = ns.Utils

local function deepCopy(source)
    if type(source) ~= "table" then return source end
    local out = {}
    for k, v in pairs(source) do out[k] = deepCopy(v) end
    return out
end

local function mergeDefaults(target, defaults)
    for k, v in pairs(defaults) do
        if type(v) == "table" then
            if type(target[k]) ~= "table" then target[k] = {} end
            mergeDefaults(target[k], v)
        elseif target[k] == nil then
            target[k] = v
        end
    end
end

function Utils:CopyDefaults() return deepCopy(ns.defaults) end
function Utils:MergeDefaults(target, defaults) mergeDefaults(target, defaults) end

function Utils:Print(message)
    print(string.format("%sAzeroth Angler|r %s", ns.constants.APP_COLOR, tostring(message or "")))
end

function Utils:Round(value)
    value = tonumber(value) or 0
    if value >= 0 then
        return math.floor(value + 0.5)
    end
    return math.ceil(value - 0.5)
end

function Utils:FormatDuration(seconds)
    seconds = math.max(0, tonumber(seconds) or 0)
    local h = math.floor(seconds / 3600)
    local m = math.floor((seconds % 3600) / 60)
    local s = math.floor(seconds % 60)
    if h > 0 then
        return string.format("%02d:%02d:%02d", h, m, s)
    end
    return string.format("%02d:%02d", m, s)
end

function Utils:GetZoneName()
    local zoneText = GetRealZoneText and GetRealZoneText() or nil
    if zoneText and zoneText ~= "" then return zoneText end
    return GetZoneText and GetZoneText() or "Unknown Zone"
end

function Utils:GetFishingSkill()
    if not (GetProfessions and GetProfessionInfo) then return 0 end
    local localizedFishingName = string.lower(tostring(ns.constants.FISHING_SPELL_NAME or "Fishing"))
    local bestRank = 0
    for i = 1, 6 do
        local professionIndex = select(i, GetProfessions())
        if professionIndex then
            local name, _, rank, _, _, _, skillLine = GetProfessionInfo(professionIndex)
            local numericRank = tonumber(rank) or 0
            local lowerName = string.lower(tostring(name or ""))
            if skillLine == 356 or lowerName == localizedFishingName or lowerName == "fishing" then
                return numericRank
            end
            if lowerName:find(localizedFishingName, 1, true) then
                if numericRank > bestRank then bestRank = numericRank end
            end
        end
    end
    return bestRank
end

function Utils:IsRareQuality(itemLink)
    if not itemLink or not GetItemInfo then return false end
    local _, _, quality = GetItemInfo(itemLink)
    return quality and quality >= 3
end

function Utils:GetItemQuality(itemLink)
    if not itemLink or not GetItemInfo then return 1 end
    local _, _, quality = GetItemInfo(itemLink)
    return tonumber(quality) or 1
end

function Utils:GetSuccessRate(catches, casts)
    catches = tonumber(catches) or 0
    casts = tonumber(casts) or 0
    if casts <= 0 then return "0%" end
    return string.format("%d%%", math.floor((catches / casts) * 100 + 0.5))
end

function Utils:CountTableEntries(tbl)
    local count = 0
    if type(tbl) ~= "table" then return 0 end
    for _ in pairs(tbl) do count = count + 1 end
    return count
end

function Utils:FormatPerHour(count, seconds, minSeconds)
    count = tonumber(count) or 0
    seconds = tonumber(seconds) or 0
    minSeconds = tonumber(minSeconds) or 0
    if count <= 0 or seconds <= 0 then return "0/hr" end
    local effectiveSeconds = math.max(1, seconds, minSeconds)
    return string.format("%d/hr", self:Round((count / effectiveSeconds) * 3600))
end

function Utils:FormatPercent(value, total)
    value = tonumber(value) or 0
    total = tonumber(total) or 0
    if total <= 0 then return "0%" end
    return string.format("%d%%", self:Round((value / total) * 100))
end


local function stripWoWColorCodes(text)
    text = tostring(text or "")
    text = text:gsub("|c%x%x%x%x%x%x%x%x", "")
    text = text:gsub("|r", "")
    return text
end

local function cleanDisplayText(value)
    local text = tostring(value or "")
    text = stripWoWColorCodes(text)
    text = text:gsub("|H.-|h%[(.-)%]|h", "%1")
    text = text:gsub("|H.-|h(.-)|h", "%1")
    text = text:gsub("|T.-|t", "")
    text = text:gsub("|A.-|a", "")
    text = text:gsub("%s+", " ")
    text = text:gsub("^%s+", "")
    text = text:gsub("%s+$", "")
    return text
end

function Utils:StripWoWColorCodes(text)
    return stripWoWColorCodes(text)
end

function Utils:CleanDisplayText(value)
    return cleanDisplayText(value)
end

function Utils:AbbrevText(value, maxLen)
    local text = cleanDisplayText(value)
    local limit = tonumber(maxLen) or 32
    if #text <= limit then return text end
    return text:sub(1, math.max(1, limit - 3)) .. "..."
end

function Utils:SafeSetWidth(fs, width)
    if fs and width and fs.SetWidth then
        fs:SetWidth(width)
        fs:SetWordWrap(false)
        if fs.SetMaxLines then fs:SetMaxLines(1) end
    end
end

function Utils:GetSpellNameByID(spellID)
    if not spellID then return nil end
    if C_Spell and C_Spell.GetSpellName then return C_Spell.GetSpellName(spellID) end
    if GetSpellInfo then return GetSpellInfo(spellID) end
    return nil
end

function Utils:BuildTopList(source, limit)
    local rows = {}
    if type(source) ~= "table" then return rows end
    for key, count in pairs(source) do
        table.insert(rows, {
            key = key,
            count = tonumber(count) or 0,
            display = self:CleanDisplayText(key),
        })
    end
    table.sort(rows, function(a, b)
        if a.count == b.count then
            return a.display < b.display
        end
        return a.count > b.count
    end)
    if limit and #rows > limit then
        while #rows > limit do
            table.remove(rows)
        end
    end
    return rows
end
