AddonDiagnostics = AddonDiagnostics or {}
AddonDiagnostics.ReportBuilder = {}
local ReportBuilder = AddonDiagnostics.ReportBuilder

ReportBuilder.SectionIDs = {}
local SectionIDs = ReportBuilder.SectionIDs
SectionIDs.SessionSummary = "session_summary"
SectionIDs.UserNotes = "user_notes"
SectionIDs.Environment = "environment"
SectionIDs.Timeline = "timeline"
SectionIDs.LuaErrors = "lua_errors"
SectionIDs.EnhancedDiagnosticsCoverage = "enhanced_diagnostics_coverage"
SectionIDs.EnhancedDiagnosticsBreadcrumbs = "enhanced_diagnostics_breadcrumbs"
SectionIDs.EnhancedDiagnosticsSpans = "enhanced_diagnostics_spans"
SectionIDs.EnhancedDiagnosticsSnapshots = "enhanced_diagnostics_snapshots"
SectionIDs.SelfHealth = "self_health"
SectionIDs.Integrity = "integrity"

local MAX_REPORT_CHARACTERS = 196000
local MAX_REPORT_SECTIONS = 18
local MAX_TIMELINE_LINES = 640
local REPORT_VERSION = "1.0.0"

local reportTruncated = false
local reportSectionCount = 0

local function ResetBuildState()
    reportTruncated = false
    reportSectionCount = 0
end

local function AppendBounded(output, addition)
    if reportTruncated then return output end
    local nextOutput = string.format("%s%s", output, addition)
    if string.len(nextOutput) <= MAX_REPORT_CHARACTERS then return nextOutput end
    local remaining = MAX_REPORT_CHARACTERS - string.len(output)
    if remaining > 0 then output = string.format("%s%s", output, string.sub(addition, 1, remaining)) end
    reportTruncated = true
    return output
end

local function AppendSection(output, title, body)
    if reportSectionCount >= MAX_REPORT_SECTIONS then
        reportTruncated = true
        return output
    end
    local safeBody = body or ""
    if safeBody == "" then safeBody = "No evidence captured." end
    reportSectionCount = reportSectionCount + 1
    return AppendBounded(output, string.format("\n## %s\n%s\n", title, safeBody))
end

local function FormatTimestamp(value)
    local timestamp = math.max(0, tonumber(value) or 0)
    if timestamp > 0 and type(date) == "function" then
        local okay, formatted = pcall(date, "%Y-%m-%d %H:%M:%S", timestamp)
        if okay and type(formatted) == "string" then return formatted end
    end
    return "time unavailable"
end

local function ParseSessionDuration(timelineEvidence)
    if type(timelineEvidence) ~= "string" or timelineEvidence == "" then return nil end
    local startIndex = 1
    local duration = nil
    for index = 1, MAX_TIMELINE_LINES do
        local endIndex = string.find(timelineEvidence, "\n", startIndex, true)
        local line = nil
        if endIndex then line = string.sub(timelineEvidence, startIndex, endIndex - 1) else line = string.sub(timelineEvidence, startIndex) end
        local marker = string.find(line, "s | SESSION | recording_stopped", 1, true)
        if marker then duration = tonumber(string.sub(line, 1, marker - 1)) end
        if not endIndex then return duration end
        startIndex = endIndex + 1
    end
    return duration
end

local function BuildSessionSummary(timelineEvidence)
    local duration = ParseSessionDuration(timelineEvidence)
    local durationText = "unavailable"
    if duration ~= nil then durationText = string.format("%.3fs", duration) end
    return string.format("Recording duration: %s\nCoverage: Universal Diagnostics plus any Enhanced Diagnostics provider coverage listed below\nReport source: sanitized completed recording evidence", durationText)
end

local function BuildSessionTriage(luaErrorEvidence)
    if type(luaErrorEvidence) == "string" and luaErrorEvidence ~= "" and luaErrorEvidence ~= "No evidence captured." then
        return "A Lua error was captured during the recording. The technical error and stack are included below. Automatic attribution is intentionally conservative because the place an error surfaces is not always the code that originally caused it."
    end
    return "No Lua error was captured during this recording. The report still contains the recording timeline, addon/build context, performance observations, and any Enhanced Diagnostics evidence that was available."
end

local function BuildIncidentEnhancedContext(incident)
    local providerID = tostring(incident.enhancedProviderID or "none")
    local breadcrumbs = tostring(incident.enhancedBreadcrumbContext or "")
    local snapshot = tostring(incident.enhancedSnapshotContext or "")
    local integrity = tostring(incident.enhancedIntegrity or "No retained Enhanced incident context.")
    if breadcrumbs == "" then breadcrumbs = "No recent passive Enhanced breadcrumbs were attached." end
    if snapshot == "" then snapshot = "No Enhanced incident snapshot was attached." end
    return string.format("Enhanced provider: %s\n\nImmediately-before-failure breadcrumbs:\n%s\n\nCurrent-state snapshot:\n%s\n\nIntegrity:\n%s", providerID, breadcrumbs, snapshot, integrity)
end

local function BuildSelfHealthSummary(sanitizer, recorderIntegrity)
    local _, _, _, sanitizedCharacters = sanitizer:GetSanitizationStats()
    local safeCharacters = math.max(0, tonumber(sanitizedCharacters) or 0)
    local integrityCharacters = 0
    local healthStatus = "degraded"
    if type(recorderIntegrity) == "string" then
        integrityCharacters = string.len(recorderIntegrity)
        if string.find(recorderIntegrity, "Recorder-source integrity: complete", 1, true) == 1 then healthStatus = "complete" end
    end
    return string.format("Recorder health status: %s\nRetained report-safe diagnostic text: approximately %d characters\nFootprint scope: retained sanitized/report-safe diagnostic text only; not total addon or Lua memory\nAdditional footprint sampling: none; derived from existing post-session bounded counters and report-safe text", healthStatus, safeCharacters + integrityCharacters)
end

local function BuildIntegritySummary(sanitizer, recorderIntegrity)
    local sections, redactions, drops, characters, truncated, ready, reason = sanitizer:GetSanitizationStats()
    return string.format("%s\nSanitized sections: %d\nRedactions: %d\nDropped values: %d\nSanitized characters: %d\nSanitizer truncated: %s\nSanitizer ready: %s\nLast sanitization reason: %s\nReport truncated: %s", recorderIntegrity, tonumber(sections) or 0, tonumber(redactions) or 0, tonumber(drops) or 0, tonumber(characters) or 0, tostring(truncated == true), tostring(ready == true), tostring(reason or "none"), tostring(reportTruncated == true))
end

function ReportBuilder:BuildIncidentReport(incident)
    ResetBuildState()
    if type(incident) ~= "table" then return "", false, false, "incident unavailable" end
    local store = AddonDiagnostics.IncidentStore
    local reportIncident = incident
    if store and type(store.GetIncidentReportView) == "function" then
        local currentView = store:GetIncidentReportView(incident)
        if type(currentView) == "table" then reportIncident = currentView end
    end
    local output = string.format("Addon Diagnostics Incident Report\nVersion: %s\nIncident: %s\n", REPORT_VERSION, tostring(incident.id or "unknown"))
    output = AppendSection(output, "What Happened", reportIncident.whatHappened)
    output = AppendSection(output, tostring(reportIncident.sourceHeading or "Likely Source"), string.format("%s\nDetected in: %s", tostring(reportIncident.likelySource or "Undetermined"), tostring(reportIncident.detectedIn or "Lua runtime")))
    output = AppendSection(output, "Confidence", tostring(reportIncident.confidence or "Low"))
    output = AppendSection(output, "What You Should Do", tostring(reportIncident.nextStep or "Review the technical details below."))
    output = AppendSection(output, "Where to Report It", tostring(reportIncident.reportDestination or "Developer/support location for the relevant addon or feature"))
    local context = string.format("Incident category: %s\nFirst seen: %s\nLast seen: %s\nOccurrences: %d\nRelevant addon version: %s\nClient/build: %s\nExplicit recorder active when last seen: %s\nBlizzard error presentation result: %s", tostring(reportIncident.category or incident.category or "Lua runtime error"), FormatTimestamp(incident.firstSeen), FormatTimestamp(incident.lastSeen), tonumber(incident.occurrences) or 1, tostring(reportIncident.sourceVersion or incident.sourceVersion or "not determined"), tostring(incident.clientBuild or "Client build unavailable"), tostring(incident.recordingActive == true), tostring(incident.presentationSuppressed or "not observed"))
    output = AppendSection(output, "Useful Context", context)
    output = AppendSection(output, "Stack Interpretation", tostring(reportIncident.stackContext or "Stack interpretation unavailable"))
    output = AppendSection(output, "Related Incidents", tostring(reportIncident.relatedContext or "Related-incident context unavailable"))
    output = AppendSection(output, "Enhanced Diagnostics Incident Context", BuildIncidentEnhancedContext(incident))
    output = AppendSection(output, "Technical Details", string.format("Message:\n%s\n\nStack:\n%s", tostring(incident.message or "unavailable"), tostring(incident.stack or "unavailable")))

    local overflow = 0
    if store and type(store.GetOverflowCount) == "function" then overflow = math.max(0, tonumber(store:GetOverflowCount()) or 0) end
    local lifecycle = string.format("Local state: %s\nCopy status: shown as COPIED LOCALLY or NOT COPIED; stored locally only and never means reported, fixed, or resolved.\nOccurrence grouping: repeated exact matching incidents update this record and make it unread/not-copied again.\nAutomatic problem-family presentation is derived separately and never merges exact incident records.\nLocal history retention: bounded to 20 unique incidents; explicit clearing removes retained local evidence only.\nUnretained incident overflow: %d", incident.reviewed == true and "reviewed" or "unread", overflow)
    output = AppendSection(output, "Incident Lifecycle / Integrity", lifecycle)
    return output, true, reportTruncated, nil
end

local function CountBoundedItems(items)
    local count = 0
    if type(items) ~= "table" then return count end
    for index = 1, 20 do
        if not items[index] then return count end
        count = count + 1
    end
    return count
end

local function CollectFamilyIncidentIDs(family)
    local ids = {}
    if type(family) ~= "table" or type(family.members) ~= "table" then return ids end
    for index = 1, 20 do
        local incident = family.members[index]
        if not incident then return ids end
        table.insert(ids, tostring(incident.id or "ADI-unknown"))
    end
    return ids
end

local function BuildFamilyVariantEvidence(family)
    local output = ""
    local shown = 0
    if type(family) ~= "table" or type(family.members) ~= "table" or type(family.leadIncident) ~= "table" then return "Variant evidence unavailable." end
    for index = 1, 20 do
        local incident = family.members[index]
        if not incident then
            if shown == 0 then return "No additional retained variants; the lead incident technical details above are the complete retained evidence for this family." end
            return output
        end
        if incident.id ~= family.leadIncident.id then
            shown = shown + 1
            output = string.format("%sAdditional variant %d - %s\nOccurrences: %d\nFirst seen: %s\nLast seen: %s\nMessage:\n%s\n\nStack:\n%s\n\nEnhanced incident context:\n%s\n\n", output, shown, tostring(incident.id or "ADI-unknown"), math.max(1, tonumber(incident.occurrences) or 1), FormatTimestamp(incident.firstSeen), FormatTimestamp(incident.lastSeen), tostring(incident.message or "unavailable"), tostring(incident.stack or "unavailable"), BuildIncidentEnhancedContext(incident))
        end
    end
    return output
end

function ReportBuilder:BuildProblemFamilyReport(family)
    if type(family) ~= "table" or type(family.leadIncident) ~= "table" then return "", false, false, "problem family unavailable", {} end
    local output, ready, truncated, reason = self:BuildIncidentReport(family.leadIncident)
    if not ready then return output, ready, truncated, reason, {} end
    output = string.gsub(output, "Addon Diagnostics Incident Report", "Addon Diagnostics Problem-Family Report", 1)
    local summary = string.format("Problem family: %s\nRetained incident variants: %d\nTotal retained occurrences: %d\nFirst seen: %s\nLast seen: %s\nGrouping is automatic and derived only from retained sanitized evidence; exact incidents remain separate forensic records.", tostring(family.label or "Unclassified problem"), math.max(1, tonumber(family.incidentCount) or 1), math.max(1, tonumber(family.occurrences) or 1), FormatTimestamp(family.firstSeen), FormatTimestamp(family.lastSeen))
    output = AppendSection(output, "Problem Family Summary", summary)
    output = AppendSection(output, "Family Variant Technical Evidence", BuildFamilyVariantEvidence(family))
    return output, true, reportTruncated, nil, CollectFamilyIncidentIDs(family)
end

local function BuildSessionReport()
    local sanitizer = AddonDiagnostics.Sanitizer
    if not sanitizer or type(sanitizer.IsSanitizationReady) ~= "function" then return "", false, false, "sanitizer unavailable" end
    if not sanitizer:IsSanitizationReady() then return "", false, false, "sanitized session evidence not ready" end
    if type(sanitizer.IsDiagnosticIntegrityReady) ~= "function" or not sanitizer:IsDiagnosticIntegrityReady() then return "", false, false, "diagnostic integrity not ready" end

    ResetBuildState()
    local environmentEvidence = sanitizer:GetSanitizedEnvironmentEvidence()
    local timelineEvidence = sanitizer:GetSanitizedTimelineEvidence()
    local luaErrorEvidence = sanitizer:GetSanitizedLuaErrorEvidence()
    local providerSummary = sanitizer:GetSanitizedProviderSummary()
    local breadcrumbEvidence = sanitizer:GetSanitizedBreadcrumbEvidence()
    local spanEvidence = sanitizer:GetSanitizedSpanEvidence()
    local snapshotEvidence = sanitizer:GetSanitizedSnapshotEvidence()
    local recorderIntegrity = sanitizer:GetSanitizedDiagnosticIntegrityEvidence()

    local output = string.format("Addon Diagnostics Recording Report\nVersion: %s\n", REPORT_VERSION)
    output = AppendSection(output, "What Happened", BuildSessionTriage(luaErrorEvidence))
    output = AppendSection(output, "Likely Source", "Undetermined from the recording alone unless the technical evidence below contains a clear addon path or Enhanced Diagnostics provider context.")
    output = AppendSection(output, "Confidence", "Low unless the technical evidence directly identifies a relevant addon path; the report does not equate the crash location with root cause.")
    output = AppendSection(output, "What You Should Do", "Send this report to the developer/support location for the addon or feature you were deliberately reproducing. Include what you expected and what actually happened.")
    output = AppendSection(output, "Where to Report It", "The addon or feature you were deliberately testing. Nothing is sent automatically by Addon Diagnostics.")
    output = AppendSection(output, "Session Summary", BuildSessionSummary(timelineEvidence))
    output = AppendSection(output, "User Notes", "Problem/reproduction notes: not captured by the current recorder.\nExpected versus actual behavior notes: not captured by the current recorder.")
    output = AppendSection(output, "Environment and Addon Inventory", environmentEvidence)
    output = AppendSection(output, "Chronological Timeline", timelineEvidence)
    output = AppendSection(output, "Captured Lua Errors", luaErrorEvidence)
    output = AppendSection(output, "Enhanced Diagnostics Coverage", providerSummary)
    output = AppendSection(output, "Enhanced Diagnostics Breadcrumbs", breadcrumbEvidence)
    output = AppendSection(output, "Enhanced Diagnostics Operation Spans", spanEvidence)
    output = AppendSection(output, "Enhanced Diagnostics Provider Snapshots", snapshotEvidence)
    output = AppendSection(output, "Recorder Self-Health / Footprint", BuildSelfHealthSummary(sanitizer, recorderIntegrity))
    output = AppendSection(output, "Diagnostic Integrity", BuildIntegritySummary(sanitizer, recorderIntegrity))
    return output, true, reportTruncated, nil
end

function ReportBuilder:BuildUncopiedProblemReports()
    local store = AddonDiagnostics.IncidentStore
    if not store or type(store.GetUncopiedProblemFamilies) ~= "function" then return "", false, false, "problem-family store unavailable", {} end
    local families = store:GetUncopiedProblemFamilies()
    local familyCount = CountBoundedItems(families)
    if familyCount == 0 then return "", false, false, "No uncopied problem reports are retained.", {} end
    local output = string.format("Addon Diagnostics Uncopied Problem Reports\nVersion: %s\nRetained uncopied problem reports: %d\nNothing is sent automatically. Copy state is local only.\n", REPORT_VERSION, familyCount)
    local includedIDs = {}
    local includedFamilies = 0
    local omittedFamilies = 0
    local accepting = true
    for familyIndex = 1, familyCount do
        local familyReport, ready, _, _, familyIDs = self:BuildProblemFamilyReport(families[familyIndex])
        local block = string.format("\n============================================================\nPROBLEM REPORT %d OF %d\n============================================================\n%s", familyIndex, familyCount, familyReport)
        if accepting and ready and string.len(output) + string.len(block) + 300 <= MAX_REPORT_CHARACTERS then
            output = string.format("%s%s", output, block)
            includedFamilies = includedFamilies + 1
            local familyIDCount = CountBoundedItems(familyIDs)
            for idIndex = 1, familyIDCount do table.insert(includedIDs, familyIDs[idIndex]) end
        elseif accepting then
            omittedFamilies = familyCount - familyIndex + 1
            accepting = false
        end
    end
    if omittedFamilies > 0 then
        output = string.format("%s\nBATCH INTEGRITY NOTICE\n%d problem report(s) were omitted because the combined payload reached its fixed character bound. Omitted reports remain NOT COPIED locally.\n", output, omittedFamilies)
    end
    output = string.format("%s\nBatch included %d of %d uncopied problem report(s).\n", output, includedFamilies, familyCount)
    return output, includedFamilies > 0, omittedFamilies > 0, nil, includedIDs
end

function ReportBuilder:BuildReport()
    local store = AddonDiagnostics.IncidentStore
    if store and type(store.GetLatestUnreadIncident) == "function" then
        local unread = store:GetLatestUnreadIncident()
        if unread then return self:BuildIncidentReport(unread) end
    end
    local sessionReport, ready, truncated, reason = BuildSessionReport()
    if ready then return sessionReport, ready, truncated, reason end
    if store and type(store.GetLatestIncident) == "function" then
        local latest = store:GetLatestIncident()
        if latest then return self:BuildIncidentReport(latest) end
    end
    return "", false, false, reason or "No incident or completed recording is available yet."
end
