GuildHelper = GuildHelper or {}

local addon = GuildHelper

local PERF_DEFAULT_THRESHOLD_MS = 8
local PERF_DEFAULT_SAMPLE_LIMIT = 80
local PERF_MAX_REPORT_SCOPES = 20
local PERF_MAX_REPORT_SAMPLES = 15
local debugProfileStop = type(debugprofilestop) == "function" and debugprofilestop or nil

local function formatMessage(...)
    local parts = {}

    for index = 1, select("#", ...) do
        parts[index] = tostring(select(index, ...))
    end

    return table.concat(parts, " ")
end

local function getPerfSettings()
    local db = _G.GuildHelperDB

    if type(db) ~= "table" then
        return nil
    end

    db.profile = type(db.profile) == "table" and db.profile or {}
    db.profile.perf = type(db.profile.perf) == "table" and db.profile.perf or {}

    local settings = db.profile.perf
    settings.enabled = settings.enabled and true or false
    settings.sampleLimit = math.max(20, math.floor(tonumber(settings.sampleLimit) or PERF_DEFAULT_SAMPLE_LIMIT))
    settings.thresholdMs = math.max(1, tonumber(settings.thresholdMs) or PERF_DEFAULT_THRESHOLD_MS)
    return settings
end

local function getPerfClockMilliseconds()
    if debugProfileStop then
        return tonumber(debugProfileStop()) or 0
    end

    if type(GetTimePreciseSec) == "function" then
        return (tonumber(GetTimePreciseSec()) or 0) * 1000
    end

    if type(GetTime) == "function" then
        return (tonumber(GetTime()) or 0) * 1000
    end

    return (tonumber(time()) or 0) * 1000
end

local function formatPerfMilliseconds(value)
    local numericValue = tonumber(value) or 0

    if numericValue >= 100 then
        return string.format("%.1fms", numericValue)
    end

    return string.format("%.2fms", numericValue)
end

function addon:GetPerfSettings()
    return getPerfSettings()
end

function addon:GetPerfRuntime()
    self._perfRuntime = self._perfRuntime or {
        recentSamples = {},
        scopes = {},
    }

    self._perfRuntime.recentSamples = self._perfRuntime.recentSamples or {}
    self._perfRuntime.scopes = self._perfRuntime.scopes or {}
    return self._perfRuntime
end

function addon:IsPerfProfilingEnabled()
    local settings = getPerfSettings()
    return settings and settings.enabled and true or false
end

function addon:ClearPerfStats()
    local runtime = self:GetPerfRuntime()
    runtime.recentSamples = {}
    runtime.scopes = {}
    return true
end

function addon:SetPerfProfilingEnabled(enabled)
    local settings = getPerfSettings()

    if not settings then
        return false
    end

    local nextEnabled = enabled and true or false
    local changed = settings.enabled ~= nextEnabled
    settings.enabled = nextEnabled

    if nextEnabled and changed then
        self:ClearPerfStats()
    end

    return settings.enabled
end

function addon:SetPerfThresholdMs(value)
    local settings = getPerfSettings()

    if not settings then
        return nil
    end

    settings.thresholdMs = math.max(1, tonumber(value) or PERF_DEFAULT_THRESHOLD_MS)
    return settings.thresholdMs
end

function addon:BeginPerfScope(scopeName)
    if not self:IsPerfProfilingEnabled() then
        return nil
    end

    return getPerfClockMilliseconds(), tostring(scopeName or "unknown")
end

local function recordPerfDurationSample(runtime, settings, scopeName, elapsedMs, recordedAt)
    local resolvedScopeName = tostring(scopeName or "unknown")
    local numericElapsedMs = math.max(0, tonumber(elapsedMs) or 0)
    local scope = runtime.scopes[resolvedScopeName] or {
        count = 0,
        maxMs = 0,
        totalMs = 0,
        slowCount = 0,
        lastMs = 0,
    }

    scope.count = scope.count + 1
    scope.totalMs = scope.totalMs + numericElapsedMs
    scope.lastMs = numericElapsedMs

    if numericElapsedMs > scope.maxMs then
        scope.maxMs = numericElapsedMs
    end

    if numericElapsedMs >= settings.thresholdMs then
        scope.slowCount = scope.slowCount + 1

        local recentSamples = runtime.recentSamples
        recentSamples[#recentSamples + 1] = {
            name = resolvedScopeName,
            recordedAt = tonumber(recordedAt) or (time and time() or 0),
            valueMs = numericElapsedMs,
        }

        while #recentSamples > settings.sampleLimit do
            table.remove(recentSamples, 1)
        end
    end

    runtime.scopes[resolvedScopeName] = scope
    return numericElapsedMs
end

function addon:EndPerfScope(scopeName, startedAt)
    local settings = getPerfSettings()

    if not settings or not settings.enabled or startedAt == nil then
        return nil
    end

    local runtime = self:GetPerfRuntime()
    local elapsedMs = math.max(0, getPerfClockMilliseconds() - (tonumber(startedAt) or 0))
    return recordPerfDurationSample(runtime, settings, scopeName, elapsedMs, time and time() or 0)
end

function addon:RecordPerfScopeDuration(scopeName, elapsedMs, recordedAt)
    local settings = getPerfSettings()

    if not settings or not settings.enabled then
        return nil
    end

    return recordPerfDurationSample(self:GetPerfRuntime(), settings, scopeName, elapsedMs, recordedAt)
end

function addon:GetPerfReportLines()
    local settings = getPerfSettings()
    local runtime = self:GetPerfRuntime()
    local summaries = {}
    local lines = {
        "GH3 Performance Profiler",
        string.format("Enabled: %s | Threshold: %s | Recorded scopes: %d", settings and settings.enabled and "Yes" or "No", formatPerfMilliseconds(settings and settings.thresholdMs or PERF_DEFAULT_THRESHOLD_MS), 0),
    }

    for scopeName, scope in pairs(runtime.scopes or {}) do
        summaries[#summaries + 1] = {
            avgMs = (tonumber(scope.count) or 0) > 0 and ((tonumber(scope.totalMs) or 0) / math.max(1, tonumber(scope.count) or 0)) or 0,
            count = tonumber(scope.count) or 0,
            maxMs = tonumber(scope.maxMs) or 0,
            name = scopeName,
            slowCount = tonumber(scope.slowCount) or 0,
            totalMs = tonumber(scope.totalMs) or 0,
        }
    end

    lines[2] = string.format("Enabled: %s | Threshold: %s | Recorded scopes: %d", settings and settings.enabled and "Yes" or "No", formatPerfMilliseconds(settings and settings.thresholdMs or PERF_DEFAULT_THRESHOLD_MS), #summaries)

    table.sort(summaries, function(left, right)
        if left.maxMs == right.maxMs then
            if left.totalMs == right.totalMs then
                return left.name < right.name
            end

            return left.totalMs > right.totalMs
        end

        return left.maxMs > right.maxMs
    end)

    if #summaries <= 0 then
        lines[#lines + 1] = "No timing data recorded yet. Run /gh3 perf on, reproduce the spike, then run /gh3 perf report."
        return lines
    end

    lines[#lines + 1] = ""
    lines[#lines + 1] = "Top Scopes"

    for index = 1, math.min(PERF_MAX_REPORT_SCOPES, #summaries) do
        local summary = summaries[index]
        lines[#lines + 1] = string.format("%02d. %s | max %s | avg %s | total %s | slow %d/%d",
            index,
            summary.name,
            formatPerfMilliseconds(summary.maxMs),
            formatPerfMilliseconds(summary.avgMs),
            formatPerfMilliseconds(summary.totalMs),
            summary.slowCount,
            summary.count)
    end

    if #(runtime.recentSamples or {}) > 0 then
        lines[#lines + 1] = ""
        lines[#lines + 1] = "Recent Slow Samples"

        local recentSamples = runtime.recentSamples
        local startIndex = math.max(1, #recentSamples - PERF_MAX_REPORT_SAMPLES + 1)

        for index = startIndex, #recentSamples do
            local sample = recentSamples[index]
            local recordedText = tonumber(sample.recordedAt) and tonumber(sample.recordedAt) > 0 and date and date("%H:%M:%S", tonumber(sample.recordedAt)) or "--:--:--"
            lines[#lines + 1] = string.format("%s | %s | %s", recordedText, formatPerfMilliseconds(sample.valueMs), tostring(sample.name or "unknown"))
        end
    end

    return lines
end

function addon:ShowPerfReport()
    local reportLines = self:GetPerfReportLines()
    local reportText = table.concat(reportLines, "\n")

    if self.MainFrame and self.MainFrame.ShowCsvExportDialog then
        self.MainFrame:ShowCsvExportDialog("GH3 Performance Report", reportText, "Use /gh3 perf clear to reset samples or /gh3 perf threshold <ms> to change the slow-scope threshold.")
        return true
    end

    for _, line in ipairs(reportLines) do
        self:Print(line)
    end

    return true
end

function addon:Print(...)
    DEFAULT_CHAT_FRAME:AddMessage("|cff5ad1e6GuildHelper v3:|r " .. formatMessage(...))
end

function addon:Debug(...)
    local db = _G.GuildHelperDB

    if db and db.profile and db.profile.debug then
        self:Print("[debug]", formatMessage(...))
    end
end
