-- ============================================================
-- FILE: Console_UI_Reports.lua
-- MODULE: MidnightUI_Console_Reports (_G.MidnightUI_Console_Reports, local M)
--
-- PURPOSE:
--   Phase 2f of the Diagnostics Console rewrite. The Reports
--   tab produces two qualitatively different outputs from the
--   same captured data:
--
--     User-Friendly  - readable prose, plain-English sentences,
--                      postable to Discord or forum threads
--                      without edits.
--     AI-Optimized   - structured key/value blocks with a
--                      machine-readable envelope, designed to
--                      be pasted directly to Claude / another
--                      assistant for diagnosis.
--
-- HOW IT WORKS:
--   Top toolbar has a segmented pill (User-Friendly / AI) that
--   swaps the active mode, an "Include Archive" checkbox that
--   folds spilled incidents into the report, and a "Copy All"
--   button that highlights the entire text area ready for the
--   player to press Ctrl+C.
--
--   Below the toolbar is a single large scrollable EditBox. On
--   mode-change or refresh, the relevant generator rebuilds the
--   report text and writes it into the EditBox.
--
-- DEPENDS ON:
--   MidnightUI_Console (engine) - GetStats, GetSessionInfo,
--                                 GetSeverityCounts, GetTopIncidents,
--                                 GetAllIncidents, GetArchivedIncidents,
--                                 GetAddonSummary.
--   MidnightUI_Console_UI (shell) - palette, fonts, helpers,
--                                   tab registry.
--
-- EXPORTS: none (registers as a tab populator).
-- EVENTS REGISTERED: none.
-- FRAMES CREATED: toolbar card, report card, scrollable edit box,
--                 mode pills, checkbox, copy button.
--
-- GOTCHAS:
--   - The report can be large (tens of thousands of lines on a
--     flood-stress session). EditBox handles that fine but
--     SetText on very long strings has an upper bound; we cap
--     total output at a configurable number of incidents.
--   - Pipe characters in captured messages would be interpreted
--     as color codes by FontString, but EditBox displays them
--     literally, so no pipe-escaping is needed here.
--   - "Copy All" works by HighlightText() selecting the edit
--     box contents so the player can Ctrl+C them out. WoW has
--     no direct clipboard API.
-- ============================================================

local _G = _G
local CreateFrame = CreateFrame
local string = _G.string
local math = _G.math
local table = _G.table
local pairs = _G.pairs
local ipairs = _G.ipairs
local pcall = _G.pcall
local tostring = _G.tostring
local time = _G.time
local date = _G.date

local M = _G.MidnightUI_Console_Reports or {}
_G.MidnightUI_Console_Reports = M

local SHELL = _G.MidnightUI_Console_UI
if not SHELL then return end

local function Engine()
    return _G.MidnightUI_Console
end

-- ============================================================
-- SECTION: Imports + Constants
-- ============================================================
local UI    = SHELL.UI
local FONT  = SHELL.FONT
local Solid = SHELL.SolidTexture
local Back  = SHELL.ApplyBackdrop
local NewFS = SHELL.NewFontString

local PAD          = 28
local GAP          = 22
local CARD_INSET   = 18
local CARD_TITLE_Y = 22
local CARD_CONTENT_Y = 64
local TOOLBAR_H    = 56

local MAX_INCIDENTS_IN_REPORT = 80   -- cap per report; tunable
local MAX_STACK_IN_REPORT     = 4000 -- per-stack cap

-- ============================================================
-- SECTION: State
-- ============================================================
local state = {
    mode            = "user",    -- "user" | "ai"
    includeArchived = false,
    body            = nil,
}

local ui = {
    toolbarCard   = nil,
    modeUserBtn   = nil, modeUserLabel = nil,
    modeAIBtn     = nil, modeAILabel   = nil,
    archiveCheck  = nil, archiveCheckBox = nil, archiveLabel  = nil,
    copyBtn       = nil,
    statsLabel    = nil,

    reportCard    = nil,
    reportScroll  = nil,
    reportBox     = nil,
    reportHint    = nil,
}

-- ============================================================
-- SECTION: Formatters
-- ============================================================
local function FormatDurationWords(sec)
    sec = tonumber(sec) or 0
    if sec < 60 then return string.format("%d seconds", sec) end
    if sec < 3600 then
        return string.format("%d minutes %d seconds",
            math.floor(sec / 60), sec % 60)
    end
    local h = math.floor(sec / 3600)
    local m = math.floor((sec % 3600) / 60)
    return string.format("%d hours %d minutes", h, m)
end

local function FormatTS(ts)
    if not ts or ts <= 0 then return "--" end
    return (date and date("%H:%M:%S", ts)) or tostring(ts)
end

local function Trim(s, max)
    if type(s) ~= "string" then return "" end
    if #s <= max then return s end
    return s:sub(1, max) .. "... [truncated]"
end

local function Join(t, sep)
    return table.concat(t, sep or "")
end

-- Gathers the entire dataset the generators work from. Called
-- once per report build so each mode sees the same snapshot.
local function Gather()
    local engine = Engine()
    local data = {
        stats    = (engine and engine.GetStats and engine.GetStats()) or {},
        session  = (engine and engine.GetSessionInfo and engine.GetSessionInfo()) or {},
        severity = (engine and engine.GetSeverityCounts and engine.GetSeverityCounts()) or {},
        addons   = (engine and engine.GetAddonSummary and engine.GetAddonSummary()) or {},
        active   = (engine and engine.GetAllIncidents and engine.GetAllIncidents()) or {},
        archived = (engine and engine.GetArchivedIncidents and engine.GetArchivedIncidents()) or {},
    }

    local pool = {}
    for i = 1, #data.active do pool[#pool + 1] = data.active[i] end
    if state.includeArchived then
        for i = 1, #data.archived do pool[#pool + 1] = data.archived[i] end
    end
    table.sort(pool, function(a, b) return (a.count or 0) > (b.count or 0) end)
    data.pool = pool

    return data
end

-- ============================================================
-- SECTION: User-Friendly Generator
-- Produces flowing prose meant for humans. No brackets, no
-- key=value pairs, no technical dumps. Reads out loud.
-- ============================================================
local function BuildUserReport()
    local d = Gather()
    local ctx = d.session.context or {}
    local now = (time and time()) or 0
    local started = d.session.startTime or 0
    local duration = (started > 0) and (now - started) or 0

    local L = {}

    L[#L+1] = "MidnightUI Diagnostics  -  Session Report"
    L[#L+1] = string.rep("=", 52)
    L[#L+1] = ""

    local identity = {}
    if ctx.player then identity[#identity+1] = ctx.player end
    if ctx.class then identity[#identity+1] = ctx.class end
    if ctx.spec then identity[#identity+1] = "(" .. ctx.spec .. ")" end
    L[#L+1] = "Character: " .. (next(identity) and table.concat(identity, " ") or "unknown")

    L[#L+1] = "Session:   #" .. tostring(d.session.session or 0)
        .. "  -  running for " .. FormatDurationWords(duration)

    local location = ctx.zone or "unknown location"
    if ctx.subzone and ctx.subzone ~= ctx.zone then
        location = location .. "  -  " .. ctx.subzone
    end
    L[#L+1] = "Location:  " .. location
    if ctx.inInstance then
        L[#L+1] = "           (inside instance: " .. tostring(ctx.instanceType or "unknown") .. ")"
    end
    if ctx.combat then
        L[#L+1] = "           (in combat at snapshot time)"
    end
    L[#L+1] = ""

    -- Activity totals
    L[#L+1] = "Activity this session"
    L[#L+1] = string.rep("-", 52)
    L[#L+1] = string.format("This session captured %d total events, which the Console",
        d.stats.totalCaptured or 0)
    L[#L+1] = string.format("deduplicated into %d unique incidents.",
        d.stats.activeIncidents or 0)
    if (d.stats.archivedIncidents or 0) > 0 then
        L[#L+1] = string.format(
            "Another %d older incidents were rolled into archive storage",
            d.stats.archivedIncidents)
        L[#L+1] = "when the active-set cap filled up."
    end
    if (d.stats.enrichQueueDepth or 0) > 0 then
        L[#L+1] = string.format("%d entries are still being enriched in the background.",
            d.stats.enrichQueueDepth)
    end
    L[#L+1] = ""

    -- Severity overview
    L[#L+1] = "Severity breakdown"
    L[#L+1] = string.rep("-", 52)
    local sev = d.severity
    local sevOrder = { "critical", "high", "medium", "low", "info" }
    local parts = {}
    for _, level in ipairs(sevOrder) do
        local n = sev[level] or 0
        if n > 0 then parts[#parts + 1] = tostring(n) .. " " .. level end
    end
    if #parts == 0 then
        L[#L+1] = "No incidents have been classified yet."
    else
        L[#L+1] = table.concat(parts, ",  ")
    end
    if (sev.critical or 0) == 0 and (sev.high or 0) == 0 then
        L[#L+1] = "Nothing critical or high-severity was captured in this session."
    end
    L[#L+1] = ""

    -- Top recurring issues
    if #d.pool > 0 then
        L[#L+1] = "Most frequent incidents"
        L[#L+1] = string.rep("-", 52)
        local count = math.min(10, #d.pool)
        for i = 1, count do
            local e = d.pool[i]
            local msg = Trim(tostring(e.message or ""), 180)
            L[#L+1] = string.format(
                "%d. Fired %d time%s  -  [%s]  -  source: %s",
                i, e.count or 1, (e.count or 1) == 1 and "" or "s",
                tostring(e.kind or "unknown"),
                tostring(e.source or e.addon or "unknown"))
            L[#L+1] = "   " .. msg
            if i < count then L[#L+1] = "" end
        end
        L[#L+1] = ""
    end

    -- Addon activity
    if #d.addons > 0 then
        L[#L+1] = "Activity by addon"
        L[#L+1] = string.rep("-", 52)
        local count = math.min(12, #d.addons)
        for i = 1, count do
            local a = d.addons[i]
            L[#L+1] = string.format(
                "%-26s  %6d events   %5d incidents   worst: %s",
                Trim(tostring(a.addon or "unknown"), 26),
                a.eventCount or 0,
                a.incidentCount or 0,
                tostring(a.worstSeverity or "info"))
        end
        L[#L+1] = ""
    end

    -- Footer
    L[#L+1] = string.rep("=", 52)
    L[#L+1] = "Report generated " .. ((date and date("%Y-%m-%d %H:%M:%S")) or "")
    L[#L+1] = "Source: MidnightUI Diagnostics Console"
    if state.includeArchived then
        L[#L+1] = "(Archived incidents are included.)"
    end

    return Join(L, "\n")
end

-- ============================================================
-- SECTION: AI-Optimized Generator
-- Structured key/value blocks in a machine-readable envelope.
-- Designed for pasting straight into Claude. Stable format so
-- future versions can be parsed reliably.
-- ============================================================
local function BuildAIReport()
    local d = Gather()
    local ctx = d.session.context or {}
    local now = (time and time()) or 0
    local started = d.session.startTime or 0
    local duration = (started > 0) and (now - started) or 0

    local L = {}

    L[#L+1] = "@@MIDNIGHTUI_CONSOLE_REPORT@@"
    L[#L+1] = "Structured session export from the MidnightUI Diagnostics Console."
    L[#L+1] = "Each block is key=value pairs or a <<< ... >>> heredoc."
    L[#L+1] = "Designed for AI-assisted triage: summarize the session, identify"
    L[#L+1] = "the highest-impact incidents, and suggest probable root causes."
    L[#L+1] = ""
    L[#L+1] = "[meta]"
    L[#L+1] = "schema=2"
    L[#L+1] = "format=ai-optimized"
    if date then
        L[#L+1] = "generated_utc=" .. date("!%Y-%m-%dT%H:%M:%SZ")
        L[#L+1] = "generated_local=" .. date("%Y-%m-%d %H:%M:%S")
    end
    L[#L+1] = "include_archived=" .. tostring(state.includeArchived)
    L[#L+1] = ""

    -- Session block
    L[#L+1] = "[session]"
    L[#L+1] = "id=" .. tostring(d.session.session or 0)
    L[#L+1] = "started_unix=" .. tostring(started)
    L[#L+1] = "duration_seconds=" .. tostring(duration)
    if ctx.player       then L[#L+1] = 'character="' .. tostring(ctx.player) .. '"' end
    if ctx.realm        then L[#L+1] = 'realm="' .. tostring(ctx.realm) .. '"' end
    if ctx.class        then L[#L+1] = "class=" .. tostring(ctx.class) end
    if ctx.spec         then L[#L+1] = 'spec="' .. tostring(ctx.spec) .. '"' end
    if ctx.level        then L[#L+1] = "level=" .. tostring(ctx.level) end
    if ctx.zone        then L[#L+1] = 'zone="' .. tostring(ctx.zone) .. '"' end
    if ctx.subzone     then L[#L+1] = 'subzone="' .. tostring(ctx.subzone) .. '"' end
    if ctx.inCombat   ~= nil then L[#L+1] = "in_combat=" .. tostring(ctx.inCombat) end
    if ctx.inInstance ~= nil then L[#L+1] = "in_instance=" .. tostring(ctx.inInstance) end
    if ctx.instanceType     then L[#L+1] = 'instance_type="' .. tostring(ctx.instanceType) .. '"' end
    if ctx.groupSize  then L[#L+1] = "group_size=" .. tostring(ctx.groupSize) end
    if ctx.client     then L[#L+1] = 'client="' .. tostring(ctx.client) .. '"' end
    if ctx.build      then L[#L+1] = 'build="' .. tostring(ctx.build) .. '"' end
    if ctx.locale     then L[#L+1] = 'locale="' .. tostring(ctx.locale) .. '"' end
    L[#L+1] = ""

    -- Totals block
    L[#L+1] = "[totals]"
    L[#L+1] = "events_captured=" .. tostring(d.stats.totalCaptured or 0)
    L[#L+1] = "cumulative_captured=" .. tostring(d.stats.cumulativeCaptured or 0)
    L[#L+1] = "active_incidents=" .. tostring(d.stats.activeIncidents or 0)
    L[#L+1] = "archived_incidents=" .. tostring(d.stats.archivedIncidents or 0)
    L[#L+1] = "enrich_queue_depth=" .. tostring(d.stats.enrichQueueDepth or 0)
    L[#L+1] = "engine_errors=" .. tostring(d.stats.engineErrors or 0)
    L[#L+1] = ""

    -- Severity block
    local sev = d.severity
    L[#L+1] = "[severity]"
    L[#L+1] = string.format(
        "critical=%d high=%d medium=%d low=%d info=%d unknown=%d",
        sev.critical or 0, sev.high or 0, sev.medium or 0,
        sev.low or 0, sev.info or 0, sev.unknown or 0)
    L[#L+1] = ""

    -- Addons block
    if #d.addons > 0 then
        L[#L+1] = "[addons] count=" .. math.min(25, #d.addons)
            .. " of_total=" .. #d.addons
        for i = 1, math.min(25, #d.addons) do
            local a = d.addons[i]
            L[#L+1] = string.format(
                "addon name=%s events=%d incidents=%d worst_severity=%s",
                tostring(a.addon or "unknown"),
                a.eventCount or 0,
                a.incidentCount or 0,
                tostring(a.worstSeverity or "info"))
        end
        L[#L+1] = ""
    end

    -- Incidents block - top N by count
    local count = math.min(MAX_INCIDENTS_IN_REPORT, #d.pool)
    L[#L+1] = "[incidents] count=" .. tostring(count)
        .. " of_total=" .. tostring(#d.pool)
        .. " sorted_by=count_desc"
    for i = 1, count do
        local e = d.pool[i]
        L[#L+1] = ""
        L[#L+1] = string.format(
            "[incident %d/%d] kind=%s count=%d first=%d last=%d severity=%s source=%s addon=%s",
            i, count,
            tostring(e.kind or "?"),
            e.count or 1,
            e.firstSeen or 0,
            e.lastSeen or 0,
            tostring(e.severity or "info"),
            tostring(e.source or "?"),
            tostring(e.addon or "?"))
        L[#L+1] = "fingerprint=" .. tostring(e.fingerprint or "?")
        if e.primary and e.primary ~= "" then
            L[#L+1] = "primary=" .. tostring(e.primary)
        end
        if e.meta and e.meta ~= "" then
            L[#L+1] = "meta=" .. tostring(e.meta)
        end

        L[#L+1] = "msg<<<"
        L[#L+1] = tostring(e.message or "")
        L[#L+1] = ">>>"

        if e.rawStack and e.rawStack ~= "" then
            L[#L+1] = "stack<<<"
            L[#L+1] = Trim(tostring(e.rawStack), MAX_STACK_IN_REPORT)
            L[#L+1] = ">>>"
        end
    end
    L[#L+1] = ""

    L[#L+1] = "@@END@@"
    return Join(L, "\n")
end

-- ============================================================
-- SECTION: Regenerate + Update Stats
-- ============================================================
local function RegenerateReport()
    if not (ui.reportBox) then return end
    local text
    if state.mode == "ai" then
        text = BuildAIReport()
    else
        text = BuildUserReport()
    end
    if type(text) ~= "string" then text = "" end
    pcall(ui.reportBox.SetText, ui.reportBox, text)
    if ui.reportScroll and ui.reportScroll.SetVerticalScroll then
        ui.reportScroll:SetVerticalScroll(0)
    end

    if ui.statsLabel then
        local modeLabel = (state.mode == "ai") and "AI-Optimized" or "User-Friendly"
        ui.statsLabel:SetText(string.format(
            "Mode: %s     Length: %d chars     Archived: %s",
            modeLabel,
            #text,
            state.includeArchived and "included" or "excluded"
        ))
    end
end

-- ============================================================
-- SECTION: Toolbar Widgets
-- ============================================================
local function StyleToolbarButton(btn, label, active)
    local bg = active and UI.accent or UI.bg
    local bgA = active and 0.22 or 0.9
    local borderA = active and 1 or 0.8
    Back(btn, bg, UI.border, bgA, borderA)
    if label then
        if active then
            label:SetTextColor(UI.accent[1], UI.accent[2], UI.accent[3])
        else
            label:SetTextColor(UI.text[1], UI.text[2], UI.text[3])
        end
    end
end

local function UpdateModePills()
    StyleToolbarButton(ui.modeUserBtn, ui.modeUserLabel, state.mode == "user")
    StyleToolbarButton(ui.modeAIBtn,   ui.modeAILabel,   state.mode == "ai")
end

local function UpdateArchiveCheck()
    if not ui.archiveCheckBox then return end
    if state.includeArchived then
        ui.archiveCheckBox:SetVertexColor(UI.accent[1], UI.accent[2], UI.accent[3], 1)
    else
        ui.archiveCheckBox:SetVertexColor(UI.muted[1], UI.muted[2], UI.muted[3], 0.4)
    end
end

local function BuildToolbar(body)
    local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
    card:SetPoint("TOPLEFT",  body, "TOPLEFT",  PAD, -PAD)
    card:SetPoint("TOPRIGHT", body, "TOPRIGHT", -PAD, -PAD)
    card:SetHeight(TOOLBAR_H)
    Back(card, UI.panel, UI.border, 1, 1)
    ui.toolbarCard = card

    -- Mode pills (left)
    local userBtn = CreateFrame("Button", nil, card, "BackdropTemplate")
    userBtn:SetSize(140, 32)
    userBtn:SetPoint("LEFT", card, "LEFT", CARD_INSET, 0)
    local userLabel = NewFS(userBtn, "OVERLAY", FONT.body, UI.text)
    userLabel:SetPoint("CENTER")
    userLabel:SetText("User-Friendly")
    ui.modeUserBtn = userBtn
    ui.modeUserLabel = userLabel

    local aiBtn = CreateFrame("Button", nil, card, "BackdropTemplate")
    aiBtn:SetSize(140, 32)
    aiBtn:SetPoint("LEFT", userBtn, "RIGHT", 8, 0)
    local aiLabel = NewFS(aiBtn, "OVERLAY", FONT.body, UI.text)
    aiLabel:SetPoint("CENTER")
    aiLabel:SetText("AI-Optimized")
    ui.modeAIBtn = aiBtn
    ui.modeAILabel = aiLabel

    userBtn:SetScript("OnClick", function()
        if state.mode ~= "user" then
            state.mode = "user"
            UpdateModePills()
            RegenerateReport()
        end
    end)
    aiBtn:SetScript("OnClick", function()
        if state.mode ~= "ai" then
            state.mode = "ai"
            UpdateModePills()
            RegenerateReport()
        end
    end)

    -- Include archived checkbox
    local check = CreateFrame("Button", nil, card, "BackdropTemplate")
    check:SetSize(20, 20)
    check:SetPoint("LEFT", aiBtn, "RIGHT", 24, 0)
    Back(check, UI.bg, UI.border, 1, 0.8)
    local checkBox = Solid(check, "OVERLAY", UI.muted, 0.4)
    checkBox:SetPoint("TOPLEFT",     check, "TOPLEFT",     3, -3)
    checkBox:SetPoint("BOTTOMRIGHT", check, "BOTTOMRIGHT", -3, 3)
    ui.archiveCheck    = check
    ui.archiveCheckBox = checkBox

    local checkLabel = NewFS(card, "OVERLAY", FONT.small, UI.text)
    checkLabel:SetPoint("LEFT", check, "RIGHT", 8, 0)
    checkLabel:SetText("Include Archived Incidents")
    ui.archiveLabel = checkLabel

    local function ToggleArchived()
        state.includeArchived = not state.includeArchived
        UpdateArchiveCheck()
        RegenerateReport()
    end
    check:SetScript("OnClick", ToggleArchived)
    -- Clicking the label also toggles (feels natural)
    checkLabel:SetParent(check)  -- keep on toolbar; rely on check's own frame

    -- Copy button (right)
    local copy = CreateFrame("Button", nil, card, "BackdropTemplate")
    copy:SetSize(140, 32)
    copy:SetPoint("RIGHT", card, "RIGHT", -CARD_INSET, 0)
    Back(copy, UI.accent, UI.border, 0.25, 1)
    local copyLabel = NewFS(copy, "OVERLAY", FONT.body, UI.accent)
    copyLabel:SetPoint("CENTER")
    copyLabel:SetText("Copy All")
    copy:SetScript("OnClick", function()
        if ui.reportBox then
            ui.reportBox:SetFocus()
            ui.reportBox:HighlightText()
            if ui.reportHint then
                ui.reportHint:SetText("Text selected  -  press Ctrl+C to copy")
                ui.reportHint:SetTextColor(UI.accent[1], UI.accent[2], UI.accent[3])
            end
        end
    end)
    copy:SetScript("OnEnter", function(self)
        self:SetBackdropColor(UI.accent[1], UI.accent[2], UI.accent[3], 0.4)
    end)
    copy:SetScript("OnLeave", function(self)
        self:SetBackdropColor(UI.accent[1], UI.accent[2], UI.accent[3], 0.25)
    end)
    ui.copyBtn = copy

    -- Stats label (right of copy, but placed below via center anchor)
    local stats = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    stats:SetPoint("RIGHT", copy, "LEFT", -18, 0)
    stats:SetJustifyH("RIGHT")
    stats:SetText("")
    ui.statsLabel = stats

    UpdateModePills()
    UpdateArchiveCheck()
end

-- ============================================================
-- SECTION: Report Card (scrollable edit box)
-- ============================================================
local function BuildReportCard(body)
    local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
    card:SetPoint("TOPLEFT",     body, "TOPLEFT",     PAD, -(PAD + TOOLBAR_H + GAP))
    card:SetPoint("BOTTOMRIGHT", body, "BOTTOMRIGHT", -PAD, PAD)
    Back(card, UI.panel, UI.border, 1, 1)
    ui.reportCard = card

    local title = NewFS(card, "OVERLAY", FONT.heading, UI.text)
    title:SetPoint("TOPLEFT", card, "TOPLEFT", CARD_INSET, -CARD_TITLE_Y)
    title:SetText("Report Output")

    local hint = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    hint:SetPoint("TOPRIGHT", card, "TOPRIGHT", -CARD_INSET, -(CARD_TITLE_Y + 3))
    hint:SetJustifyH("RIGHT")
    hint:SetText("Click inside, then Ctrl+A / Ctrl+C to copy")
    ui.reportHint = hint

    local scroll = CreateFrame("ScrollFrame", nil, card, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT",     card, "TOPLEFT",     CARD_INSET,  -CARD_CONTENT_Y)
    scroll:SetPoint("BOTTOMRIGHT", card, "BOTTOMRIGHT", -30,          CARD_INSET)

    local box = CreateFrame("EditBox", nil, scroll)
    box:SetMultiLine(true)
    box:SetAutoFocus(false)
    box:SetFontObject("ChatFontNormal")
    box:SetWidth(scroll:GetWidth() ~= 0 and scroll:GetWidth() or 1100)
    box:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    box:SetScript("OnTextChanged",   function(self) end)
    box:SetTextInsets(8, 8, 6, 6)
    scroll:SetScrollChild(box)

    -- Resize the edit box to the scroll frame on every show so long
    -- reports wrap properly.
    scroll:SetScript("OnSizeChanged", function(self, w, h)
        box:SetWidth(math.max(100, w - 16))
    end)

    ui.reportScroll = scroll
    ui.reportBox    = box
end

-- ============================================================
-- SECTION: Build + Refresh
-- ============================================================
local function BuildReports(body)
    state.body = body
    BuildToolbar(body)
    BuildReportCard(body)
end

local function RefreshReports()
    if not ui.reportBox then return end
    UpdateModePills()
    UpdateArchiveCheck()
    RegenerateReport()
    if ui.reportHint then
        ui.reportHint:SetText("Click inside, then Ctrl+A / Ctrl+C to copy")
        ui.reportHint:SetTextColor(UI.muted[1], UI.muted[2], UI.muted[3])
    end
end

SHELL.RegisterTabContent("reports", BuildReports, RefreshReports)

-- End of Console_UI_Reports.lua
