-- ============================================================
-- FILE: Console_UI_Archive.lua
-- MODULE: MidnightUI_Console_Archive (_G.MidnightUI_Console_Archive, local M)
--
-- PURPOSE:
--   Phase 2d of the Diagnostics Console rewrite. The Archive
--   tab is the historical counterpart to the Stream tab: it
--   browses spilled incidents that were rolled out of the
--   active set when it hit the 5000-unique soft cap. Archive
--   storage also preserves incidents from prior sessions.
--
--   Layout mirrors Stream (toolbar, scrollable feed, inspector)
--   but draws from MidnightUIConsoleDB.archive rather than the
--   active table, has no live ticker (archive is static while
--   you view it), and adds a session filter to narrow the list
--   to a single session.
--
-- HOW IT WORKS:
--   Toolbar:
--     - Search box (substring match, live)
--     - Session dropdown button (All / Session #N)
--     - Result count
--   Feed card:
--     - FauxScrollFrame-backed row pool, same virtualization
--       idiom as Stream so 10,000 archived rows perform as
--       well as 10.
--     - Rows sort by lastSeen descending.
--   Inspector:
--     - Click a row to see the full incident: kind, count,
--       source, session, first/last seen, message, stack.
--
--   The session dropdown is a small custom popup rather than
--   UIDropDownMenu - keeps the visual language consistent with
--   the rest of the Console.
--
-- DEPENDS ON:
--   MidnightUI_Console (engine) - GetArchivedIncidents,
--                                 GetSessionIds,
--                                 GetIncidentByFingerprint.
--   MidnightUI_Console_UI (shell) - palette, fonts, helpers,
--                                   tab registry.
--
-- EXPORTS: none (registers as a tab populator).
-- EVENTS REGISTERED: none.
-- FRAMES CREATED: toolbar, feed, inspector, row pool,
--                 session dropdown popup.
--
-- GOTCHAS:
--   - The session dropdown popup anchors to the session button
--     and lives at a higher frame strata so it draws above
--     other Console widgets.
--   - Row pool size tuned to the feed's scroll frame height so
--     rows never overflow the card (same as Stream).
--   - GetIncidentByFingerprint only queries the active store;
--     for archived incidents we resolve the fingerprint locally
--     against the filtered list we built.
-- ============================================================

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 FauxScrollFrame_OnVerticalScroll = _G.FauxScrollFrame_OnVerticalScroll
local FauxScrollFrame_Update = _G.FauxScrollFrame_Update
local FauxScrollFrame_GetOffset = _G.FauxScrollFrame_GetOffset

local M = _G.MidnightUI_Console_Archive or {}
_G.MidnightUI_Console_Archive = 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 SEV = {
    critical = {0.92, 0.30, 0.30},
    high     = {1.00, 0.55, 0.15},
    medium   = {1.00, 0.80, 0.20},
    low      = {0.65, 0.45, 0.90},
    info     = {0.35, 0.75, 0.95},
}

local KIND_LABELS = {
    LuaError = "Error",
    Taint    = "Taint",
    Warning  = "Warning",
    UIError  = "UI",
    Debug    = "Debug",
}

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 ROW_H          = 42
local ROW_GAP        = 6
local ROW_STRIDE     = ROW_H + ROW_GAP
local POOL_SIZE      = 9
local INSPECTOR_W    = 440

-- ============================================================
-- SECTION: State
-- ============================================================
local state = {
    searchText   = "",
    sessionFilter = "all",   -- "all" or a numeric session id
    selectedFP   = nil,
    filtered     = {},
    body         = nil,
}

local feed = {
    card       = nil,
    scroll     = nil,
    rows       = {},
    countLabel = nil,
    emptyLabel = nil,
}

local inspector = {
    card         = nil,
    empty        = nil,
    kindBadge    = nil,
    kindText     = nil,
    countLabel   = nil,
    sourceLabel  = nil,
    firstLabel   = nil,
    lastLabel    = nil,
    sessionLabel = nil,
    msgHeader    = nil,
    messageScroll = nil,
    messageBox   = nil,
    stackHeader  = nil,
    stackScroll  = nil,
    stackBox     = nil,
    _toggle      = nil,
}

local toolbar = {
    card         = nil,
    searchBox    = nil,
    sessionBtn   = nil,
    sessionLabel = nil,
    popup        = nil,
    statusLabel  = nil,
}

local SelectIncident
local UpdateRows
local RefreshArchive
local BuildSessionPopup

-- ============================================================
-- SECTION: Helpers
-- ============================================================
local function Truncate(s, maxLen)
    if type(s) ~= "string" then return "" end
    if #s <= maxLen then return s end
    return s:sub(1, maxLen) .. "..."
end

local function FormatRelTime(ts)
    if type(ts) ~= "number" or ts <= 0 then return "--" end
    local now = (time and time()) or 0
    local delta = now - ts
    if delta < 5    then return "just now" end
    if delta < 60   then return string.format("%ds ago", delta) end
    if delta < 3600 then return string.format("%dm ago", math.floor(delta / 60)) end
    if delta < 86400 then return string.format("%dh ago", math.floor(delta / 3600)) end
    return (date and date("%m/%d %H:%M", ts)) or "earlier"
end

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

local function ColorForKind(kind)
    if kind == "LuaError" then return SEV.critical end
    if kind == "Taint"    then return SEV.high end
    if kind == "Warning"  then return SEV.medium end
    if kind == "UIError"  then return SEV.low end
    return SEV.info
end

local function SetText(fs, text)
    if fs then fs:SetText(text or "") end
end

-- ============================================================
-- SECTION: Filter Pipeline
-- ============================================================
local function ApplyFilters(all)
    local needle = (state.searchText or ""):lower()
    local wantSession = state.sessionFilter
    local out = {}
    for i = 1, #all do
        local e = all[i]
        local keep = true
        if wantSession ~= "all" and e.session ~= wantSession then
            keep = false
        end
        if keep and needle ~= "" then
            local hit = false
            if e.message and string.find(string.lower(e.message), needle, 1, true) then
                hit = true
            elseif e.source and string.find(string.lower(e.source), needle, 1, true) then
                hit = true
            elseif e.addon and string.find(string.lower(e.addon), needle, 1, true) then
                hit = true
            elseif e.kind and string.find(string.lower(e.kind), needle, 1, true) then
                hit = true
            end
            keep = hit
        end
        if keep then out[#out + 1] = e end
    end
    return out
end

local function RecomputeFiltered()
    local engine = Engine()
    local all = (engine and engine.GetArchivedIncidents
                  and engine.GetArchivedIncidents()) or {}
    local filtered = ApplyFilters(all)
    table.sort(filtered, function(a, b)
        return (a.lastSeen or 0) > (b.lastSeen or 0)
    end)
    state.filtered = filtered
end

local function FindIncidentByFP(fp)
    for i = 1, #state.filtered do
        local e = state.filtered[i]
        if e.fingerprint == fp then return e end
    end
    return nil
end

-- ============================================================
-- SECTION: Row Construction (pooled)
-- ============================================================
local function BuildRow(parent, index)
    local row = CreateFrame("Button", nil, parent)
    row:SetHeight(ROW_H)
    row:EnableMouse(true)

    local bg = Solid(row, "BACKGROUND", UI.inset, 1)
    bg:SetAllPoints()
    row.bg = bg

    local sevBar = Solid(row, "ARTWORK", SEV.info, 1)
    sevBar:SetWidth(4)
    sevBar:SetPoint("TOPLEFT",    row, "TOPLEFT",    0, 0)
    sevBar:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
    row.sevBar = sevBar

    local kindBadge = CreateFrame("Frame", nil, row, "BackdropTemplate")
    kindBadge:SetSize(58, 22)
    kindBadge:SetPoint("LEFT", row, "LEFT", 14, 0)
    Back(kindBadge, UI.bg, UI.border, 1, 0.7)
    local kindText = NewFS(kindBadge, "OVERLAY", FONT.tiny, UI.text)
    kindText:SetPoint("CENTER")
    row.kindBadge = kindBadge
    row.kindText  = kindText

    local countPill = CreateFrame("Frame", nil, row, "BackdropTemplate")
    countPill:SetSize(52, 22)
    countPill:SetPoint("LEFT", kindBadge, "RIGHT", 8, 0)
    Back(countPill, UI.bg, UI.border, 1, 0.7)
    local countText = NewFS(countPill, "OVERLAY", FONT.tiny, UI.accent)
    countText:SetPoint("CENTER")
    row.countText = countText

    local msg = NewFS(row, "OVERLAY", FONT.body, UI.text)
    msg:SetPoint("LEFT", countPill, "RIGHT", 14, 0)
    msg:SetJustifyH("LEFT")
    msg:SetPoint("RIGHT", row, "RIGHT", -220, 0)
    row.msg = msg

    local src = NewFS(row, "OVERLAY", FONT.small, UI.muted)
    src:SetPoint("RIGHT", row, "RIGHT", -110, 0)
    src:SetJustifyH("RIGHT")
    row.src = src

    local sess = NewFS(row, "OVERLAY", FONT.small, UI.muted)
    sess:SetPoint("RIGHT", row, "RIGHT", -14, 0)
    sess:SetJustifyH("RIGHT")
    row.sess = sess

    row:SetScript("OnEnter", function(self)
        if self.fp == state.selectedFP then return end
        self.bg:SetVertexColor(UI.rowHover[1], UI.rowHover[2], UI.rowHover[3], 1)
    end)
    row:SetScript("OnLeave", function(self)
        if self.fp == state.selectedFP then return end
        self.bg:SetVertexColor(UI.inset[1], UI.inset[2], UI.inset[3], 1)
    end)
    row:SetScript("OnClick", function(self)
        if self.fp then SelectIncident(self.fp) end
    end)

    row:Hide()
    return row
end

-- ============================================================
-- SECTION: Feed Card
-- ============================================================
local function BuildFeed(body)
    local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
    card:SetPoint("TOPLEFT",     body, "TOPLEFT",     PAD, -(PAD + TOOLBAR_H + GAP))
    card:SetPoint("BOTTOMLEFT",  body, "BOTTOMLEFT",  PAD, PAD)
    card:SetPoint("RIGHT",       body, "RIGHT",       -(PAD + INSPECTOR_W + GAP), 0)
    Back(card, UI.panel, UI.border, 1, 1)
    feed.card = card

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

    feed.countLabel = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    feed.countLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -CARD_INSET, -(CARD_TITLE_Y + 3))
    feed.countLabel:SetJustifyH("RIGHT")

    local scroll = CreateFrame("ScrollFrame", "MidnightUI_Console_ArchiveScroll",
                               card, "FauxScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT",     card, "TOPLEFT",     CARD_INSET, -CARD_CONTENT_Y)
    scroll:SetPoint("BOTTOMRIGHT", card, "BOTTOMRIGHT", -32,         CARD_INSET)
    scroll:SetScript("OnVerticalScroll", function(self, offset)
        FauxScrollFrame_OnVerticalScroll(self, offset, ROW_STRIDE, UpdateRows)
    end)
    scroll:SetScript("OnShow", function() UpdateRows() end)
    feed.scroll = scroll

    for i = 1, POOL_SIZE do
        feed.rows[i] = BuildRow(card, i)
    end

    feed.emptyLabel = NewFS(card, "OVERLAY", FONT.body, UI.muted)
    feed.emptyLabel:SetPoint("CENTER", card, "CENTER", 0, 0)
    feed.emptyLabel:SetText("No Archived Incidents")
    feed.emptyLabel:Hide()
end

function UpdateRows()
    if not feed.scroll then return end

    local total = #state.filtered
    FauxScrollFrame_Update(feed.scroll, total, POOL_SIZE, ROW_STRIDE)

    local offset = FauxScrollFrame_GetOffset(feed.scroll) or 0

    if total == 0 then
        feed.emptyLabel:Show()
        for i = 1, POOL_SIZE do feed.rows[i]:Hide() end
        SetText(feed.countLabel, "0")
        return
    end
    feed.emptyLabel:Hide()
    SetText(feed.countLabel, tostring(total) .. " shown")

    for i = 1, POOL_SIZE do
        local row = feed.rows[i]
        local e = state.filtered[offset + i]
        if e then
            row:ClearAllPoints()
            row:SetPoint("TOPLEFT",  feed.scroll, "TOPLEFT",  0, -((i - 1) * ROW_STRIDE))
            row:SetPoint("TOPRIGHT", feed.scroll, "TOPRIGHT", 0, -((i - 1) * ROW_STRIDE))
            row.fp = e.fingerprint

            local color = ColorForKind(e.kind)
            row.sevBar:SetVertexColor(color[1], color[2], color[3], 1)

            SetText(row.kindText,  KIND_LABELS[e.kind or ""] or (e.kind or "?"))
            SetText(row.countText, "x" .. tostring(e.count or 1))
            SetText(row.msg,       Truncate(tostring(e.message or ""), 76))
            SetText(row.src,       Truncate(tostring(e.source or e.addon or "?"), 16))
            SetText(row.sess,      "S#" .. tostring(e.session or 0))

            if e.fingerprint == state.selectedFP then
                row.bg:SetVertexColor(UI.rowHover[1] * 1.5, UI.rowHover[2] * 1.5,
                                       UI.rowHover[3] * 1.5, 1)
            else
                row.bg:SetVertexColor(UI.inset[1], UI.inset[2], UI.inset[3], 1)
            end
            row:Show()
        else
            row:Hide()
            row.fp = nil
        end
    end
end

-- ============================================================
-- SECTION: Inspector
-- ============================================================
local function BuildInspector(body)
    local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
    card:SetPoint("TOPRIGHT",    body, "TOPRIGHT",    -PAD, -(PAD + TOOLBAR_H + GAP))
    card:SetPoint("BOTTOMRIGHT", body, "BOTTOMRIGHT", -PAD, PAD)
    card:SetWidth(INSPECTOR_W)
    Back(card, UI.panel, UI.border, 1, 1)
    inspector.card = card

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

    inspector.empty = NewFS(card, "OVERLAY", FONT.body, UI.muted)
    inspector.empty:SetPoint("CENTER", card, "CENTER", 0, 0)
    inspector.empty:SetText("Select An Incident")

    inspector.kindBadge = CreateFrame("Frame", nil, card, "BackdropTemplate")
    inspector.kindBadge:SetSize(80, 24)
    inspector.kindBadge:SetPoint("TOPLEFT", card, "TOPLEFT", CARD_INSET, -CARD_CONTENT_Y)
    Back(inspector.kindBadge, UI.bg, UI.border, 1, 0.8)
    inspector.kindText = NewFS(inspector.kindBadge, "OVERLAY", FONT.tiny, UI.text)
    inspector.kindText:SetPoint("CENTER")

    inspector.countLabel = NewFS(card, "OVERLAY", FONT.body, UI.accent)
    inspector.countLabel:SetPoint("LEFT", inspector.kindBadge, "RIGHT", 10, 0)

    inspector.sourceLabel = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    inspector.sourceLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -CARD_INSET, -(CARD_CONTENT_Y + 4))
    inspector.sourceLabel:SetJustifyH("RIGHT")

    local timesY = CARD_CONTENT_Y + 36
    inspector.firstLabel = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    inspector.firstLabel:SetPoint("TOPLEFT", card, "TOPLEFT", CARD_INSET, -timesY)

    inspector.lastLabel = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    inspector.lastLabel:SetPoint("TOPLEFT", card, "TOPLEFT", CARD_INSET, -(timesY + 18))

    inspector.sessionLabel = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    inspector.sessionLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -CARD_INSET, -timesY)
    inspector.sessionLabel:SetJustifyH("RIGHT")

    local msgHeaderY = timesY + 48
    inspector.msgHeader = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    inspector.msgHeader:SetPoint("TOPLEFT", card, "TOPLEFT", CARD_INSET, -msgHeaderY)
    inspector.msgHeader:SetText("Message")

    local msgScroll = CreateFrame("ScrollFrame", nil, card, "UIPanelScrollFrameTemplate")
    msgScroll:SetPoint("TOPLEFT",  card, "TOPLEFT",  CARD_INSET, -(msgHeaderY + 20))
    msgScroll:SetPoint("TOPRIGHT", card, "TOPRIGHT", -36,          -(msgHeaderY + 20))
    msgScroll:SetHeight(120)
    local msgBox = CreateFrame("EditBox", nil, msgScroll)
    msgBox:SetMultiLine(true)
    msgBox:SetAutoFocus(false)
    msgBox:SetFontObject("ChatFontNormal")
    msgBox:SetWidth(INSPECTOR_W - CARD_INSET - 42)
    msgBox:SetScript("OnEscapePressed", msgBox.ClearFocus)
    msgScroll:SetScrollChild(msgBox)
    inspector.messageScroll = msgScroll
    inspector.messageBox    = msgBox

    local stackHeaderY = msgHeaderY + 20 + 120 + 16
    inspector.stackHeader = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    inspector.stackHeader:SetPoint("TOPLEFT", card, "TOPLEFT", CARD_INSET, -stackHeaderY)
    inspector.stackHeader:SetText("Stack Trace")

    local stackScroll = CreateFrame("ScrollFrame", nil, card, "UIPanelScrollFrameTemplate")
    stackScroll:SetPoint("TOPLEFT",     card, "TOPLEFT",     CARD_INSET, -(stackHeaderY + 20))
    stackScroll:SetPoint("BOTTOMRIGHT", card, "BOTTOMRIGHT", -36,          CARD_INSET)
    local stackBox = CreateFrame("EditBox", nil, stackScroll)
    stackBox:SetMultiLine(true)
    stackBox:SetAutoFocus(false)
    stackBox:SetFontObject("ChatFontNormal")
    stackBox:SetWidth(INSPECTOR_W - CARD_INSET - 42)
    stackBox:SetScript("OnEscapePressed", stackBox.ClearFocus)
    stackScroll:SetScrollChild(stackBox)
    inspector.stackScroll = stackScroll
    inspector.stackBox    = stackBox

    local function Toggle(show)
        local widgets = {
            inspector.kindBadge, inspector.countLabel, inspector.sourceLabel,
            inspector.firstLabel, inspector.lastLabel, inspector.sessionLabel,
            inspector.msgHeader, inspector.messageScroll,
            inspector.stackHeader, inspector.stackScroll,
        }
        for _, w in ipairs(widgets) do
            if show then w:Show() else w:Hide() end
        end
    end
    inspector._toggle = Toggle
    Toggle(false)
end

local function RefreshInspector()
    if not inspector.card then return end
    local e = state.selectedFP and FindIncidentByFP(state.selectedFP) or nil

    if not e then
        inspector._toggle(false)
        inspector.empty:Show()
        return
    end
    inspector.empty:Hide()
    inspector._toggle(true)

    local color = ColorForKind(e.kind)
    inspector.kindBadge:SetBackdropBorderColor(color[1], color[2], color[3], 0.8)
    SetText(inspector.kindText, KIND_LABELS[e.kind or ""] or (e.kind or "Unknown"))
    SetText(inspector.countLabel, "x" .. tostring(e.count or 1))
    SetText(inspector.sourceLabel, tostring(e.source or e.addon or "?"))
    SetText(inspector.firstLabel,   "First Seen:  " .. FormatAbsTime(e.firstSeen))
    SetText(inspector.lastLabel,    "Last Seen:   " .. FormatAbsTime(e.lastSeen))
    SetText(inspector.sessionLabel, "Session #" .. tostring(e.session or 0))
    inspector.messageBox:SetText(tostring(e.message or ""))
    local stackText = tostring(e.rawStack or "")
    if stackText == "" then stackText = "(no stack captured)" end
    inspector.stackBox:SetText(stackText)
end

SelectIncident = function(fp)
    state.selectedFP = fp
    UpdateRows()
    RefreshInspector()
end

-- ============================================================
-- SECTION: Session Filter Dropdown
-- Small custom popup. Clicking the toolbar session button toggles
-- it open/closed; clicking an entry filters and closes.
-- ============================================================
BuildSessionPopup = function()
    if toolbar.popup then return toolbar.popup end
    -- Parented to UIParent (not toolbar.card) so we can set a
    -- strata above the Console's FULLSCREEN_DIALOG without
    -- inheriting back down. TOOLTIP is the top of WoW's strata
    -- stack, so the dropdown is guaranteed to render above
    -- every Console surface including the tab content cards.
    local popup = CreateFrame("Frame", nil, _G.UIParent, "BackdropTemplate")
    popup:SetFrameStrata("TOOLTIP")
    popup:SetToplevel(true)
    popup:SetSize(160, 200)
    popup:SetPoint("TOPLEFT", toolbar.sessionBtn, "BOTTOMLEFT", 0, -4)
    Back(popup, UI.inset, UI.border, 1, 1)
    popup:Hide()

    popup.entries = {}
    toolbar.popup = popup
    return popup
end

local function CloseSessionPopup()
    if toolbar.popup then toolbar.popup:Hide() end
end

local function RefreshSessionPopup()
    if not toolbar.popup then return end
    local popup = toolbar.popup

    for _, e in ipairs(popup.entries) do e:Hide() end

    local engine = Engine()
    local ids = (engine and engine.GetSessionIds and engine.GetSessionIds()) or {}

    local items = { { label = "All Sessions", value = "all" } }
    for _, id in ipairs(ids) do
        items[#items + 1] = { label = "Session #" .. id, value = id }
    end

    local rowH = 26
    popup:SetHeight(10 + #items * rowH)

    for i, item in ipairs(items) do
        local btn = popup.entries[i]
        if not btn then
            btn = CreateFrame("Button", nil, popup)
            btn:SetHeight(rowH)
            btn:SetPoint("TOPLEFT",  popup, "TOPLEFT",  5, -(5 + (i - 1) * rowH))
            btn:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -5, -(5 + (i - 1) * rowH))
            local label = NewFS(btn, "OVERLAY", FONT.body, UI.text)
            label:SetPoint("LEFT", btn, "LEFT", 10, 0)
            btn.label = label
            local bg = Solid(btn, "BACKGROUND", UI.inset, 0)
            bg:SetAllPoints()
            btn.bg = bg
            btn:SetScript("OnEnter", function(self)
                self.bg:SetVertexColor(UI.rowHover[1], UI.rowHover[2], UI.rowHover[3], 1)
            end)
            btn:SetScript("OnLeave", function(self)
                self.bg:SetVertexColor(UI.inset[1], UI.inset[2], UI.inset[3], 0)
            end)
            popup.entries[i] = btn
        end
        btn.label:SetText(item.label)
        if state.sessionFilter == item.value then
            btn.label:SetTextColor(UI.accent[1], UI.accent[2], UI.accent[3])
        else
            btn.label:SetTextColor(UI.text[1], UI.text[2], UI.text[3])
        end
        btn:SetScript("OnClick", function()
            state.sessionFilter = item.value
            SetText(toolbar.sessionLabel,
                (item.value == "all") and "Session: All" or ("Session: #" .. item.value))
            CloseSessionPopup()
            RefreshArchive()
        end)
        btn:Show()
    end
end

-- ============================================================
-- SECTION: Toolbar
-- ============================================================
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)
    toolbar.card = card

    -- Search box
    local searchWrap = CreateFrame("Frame", nil, card, "BackdropTemplate")
    searchWrap:SetSize(360, 30)
    searchWrap:SetPoint("LEFT", card, "LEFT", CARD_INSET, 0)
    Back(searchWrap, UI.bg, UI.border, 1, 0.9)

    local searchLabel = NewFS(searchWrap, "OVERLAY", FONT.small, UI.muted)
    searchLabel:SetPoint("LEFT", searchWrap, "LEFT", 10, 0)
    searchLabel:SetText("Search:")

    local searchBox = CreateFrame("EditBox", nil, searchWrap)
    searchBox:SetPoint("LEFT",  searchLabel, "RIGHT", 8, 0)
    searchBox:SetPoint("RIGHT", searchWrap,  "RIGHT", -8, 0)
    searchBox:SetHeight(24)
    searchBox:SetFontObject("ChatFontNormal")
    searchBox:SetAutoFocus(false)
    searchBox:SetTextColor(UI.text[1], UI.text[2], UI.text[3])
    searchBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    searchBox:SetScript("OnEnterPressed",  function(self) self:ClearFocus() end)
    searchBox:SetScript("OnTextChanged",   function(self)
        state.searchText = self:GetText() or ""
        RefreshArchive()
    end)
    toolbar.searchBox = searchBox

    -- Session filter button (right)
    local sessBtn = CreateFrame("Button", nil, card, "BackdropTemplate")
    sessBtn:SetSize(160, 30)
    sessBtn:SetPoint("RIGHT", card, "RIGHT", -CARD_INSET, 0)
    Back(sessBtn, UI.bg, UI.border, 1, 0.9)
    local sessLabel = NewFS(sessBtn, "OVERLAY", FONT.body, UI.text)
    sessLabel:SetPoint("LEFT", sessBtn, "LEFT", 12, 0)
    sessLabel:SetText("Session: All")
    local chev = NewFS(sessBtn, "OVERLAY", FONT.small, UI.muted)
    chev:SetPoint("RIGHT", sessBtn, "RIGHT", -10, 0)
    chev:SetText("v")
    toolbar.sessionBtn   = sessBtn
    toolbar.sessionLabel = sessLabel

    sessBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(UI.rowHover[1], UI.rowHover[2], UI.rowHover[3], 1)
    end)
    sessBtn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(UI.bg[1], UI.bg[2], UI.bg[3], 0.9)
    end)
    sessBtn:SetScript("OnClick", function()
        local popup = BuildSessionPopup()
        if popup:IsShown() then
            popup:Hide()
        else
            RefreshSessionPopup()
            popup:Show()
        end
    end)

    -- Status text (center)
    local status = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    status:SetPoint("CENTER", card, "CENTER", 0, 0)
    status:SetText("")
    toolbar.statusLabel = status
end

local function RefreshToolbar()
    if toolbar.statusLabel then
        local n = #state.filtered
        toolbar.statusLabel:SetText(string.format("%d archived %s",
            n, n == 1 and "incident" or "incidents"))
    end
end

-- ============================================================
-- SECTION: Build + Refresh
-- ============================================================
local function BuildArchive(body)
    state.body = body
    BuildToolbar(body)
    BuildFeed(body)
    BuildInspector(body)
end

RefreshArchive = function()
    if not feed.card then return end
    RecomputeFiltered()
    UpdateRows()
    RefreshInspector()
    RefreshToolbar()
end

-- ============================================================
-- SECTION: Registration
-- ============================================================
SHELL.RegisterTabContent("archive", BuildArchive, RefreshArchive)

-- End of Console_UI_Archive.lua
