-- ============================================================
-- FILE: Console_UI_Overview.lua
-- MODULE: MidnightUI_Console_Overview (_G.MidnightUI_Console_Overview, local M)
--
-- PURPOSE:
--   Phase 2b of the Diagnostics Console rewrite. Populates the
--   Overview tab with the session-at-a-glance landing view: a
--   hero card with character / spec / zone / session duration /
--   total counts, a severity pill row, a Top Incidents card on
--   the left, and an Addon Health grid on the right.
--
-- HOW IT WORKS:
--   Registers a build/refresh pair with Console_UI.lua via
--   RegisterTabContent("overview", ...). build() runs once the
--   first time the Overview body is constructed; it lays out
--   the three cards and their child widgets. refresh() runs
--   whenever the window opens or the Overview tab is activated;
--   it queries the engine (GetSessionInfo, GetStats,
--   GetSeverityCounts, GetTopIncidents, GetAddonSummary) and
--   updates the displayed values in place. Frames are reused
--   between refreshes — no teardown/rebuild.
--
-- DEPENDS ON:
--   MidnightUI_Console (engine)      — all data queries.
--   MidnightUI_Console_UI (shell)    — palette, layout constants,
--                                      style helpers, tab registry.
--
-- EXPORTS: none (registers as a tab populator).
--
-- EVENTS REGISTERED: none.
-- FRAMES CREATED: hero, top-incidents, and addon-grid cards plus
-- their child rows/tiles, all parented to the Overview body frame.
--
-- GOTCHAS:
--   - build() runs once; refresh() runs often. Anything that
--     depends on engine state must live in refresh(), not build().
--   - Rows and tiles are pooled — refresh() hides unused ones
--     rather than destroying them, so frame count is bounded.
-- ============================================================

local _G = _G
local CreateFrame = CreateFrame
local string = _G.string
local math = _G.math
local pairs = _G.pairs
local ipairs = _G.ipairs
local time = _G.time
local pcall = _G.pcall
local C_Timer = _G.C_Timer

local REFRESH_INTERVAL = 1.0   -- seconds; drives the live Session Duration tick

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

-- ============================================================
-- SECTION: Shell + Engine References
-- ============================================================
local SHELL = _G.MidnightUI_Console_UI
if not SHELL then return end  -- shell missing; bail silently

local ENGINE_NAME = "MidnightUI_Console"

local function Engine()
    return _G[ENGINE_NAME]
end

-- ============================================================
-- SECTION: Local Palette + Layout
-- Reuses the shell's palette and adds a severity-color table.
-- ============================================================
local UI     = SHELL.UI
local LAYOUT = SHELL.LAYOUT
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},
    unknown  = {0.55, 0.55, 0.55},
}

local SEVERITY_ORDER = { "critical", "high", "medium", "low", "info" }
local SEVERITY_LABEL = {
    critical = "Critical",
    high     = "High",
    medium   = "Medium",
    low      = "Low",
    info     = "Info",
}

-- Outer padding inside the body and gaps between cards. Generous
-- values here are deliberate — the content should feel airy, not
-- cramped. These numbers drive every subsequent positioning call.
local PAD = 28
local GAP = 22

-- Card interior spacing constants so titles, rows, and tiles all
-- use the same visual rhythm rather than ad-hoc magic numbers.
local CARD_TITLE_Y   = 22   -- title offset from card top
local CARD_CONTENT_Y = 64   -- content area begins this far from card top
                             -- (reduced because subtitles were removed)

-- Hero card height must match the value used by BuildHero AND the
-- Y-offset math in BuildTopIncidents / BuildAddonGrid. Kept as a
-- single constant so the three cannot drift out of sync.
local HERO_HEIGHT = 196

-- ============================================================
-- SECTION: References to Built Widgets (filled by build())
-- refresh() reads from these to update displayed values without
-- tearing anything down.
-- ============================================================
local hero = {
    card = nil,
    identity = nil,   -- character/spec FontString
    zone = nil,
    sessionTag = nil,
    duration = nil,
    eventsLabel = nil,
    incidentsLabel = nil,
    archivedLabel = nil,
    pills = {},       -- severity -> { frame, count }
}

local top = {
    card = nil,
    empty = nil,      -- "no incidents yet" placeholder
    rows = {},        -- pool of row frames
}

local grid = {
    card = nil,
    empty = nil,
    tiles = {},       -- pool of addon tiles
}

-- ============================================================
-- SECTION: Small Utilities
-- ============================================================
local function FormatDuration(sec)
    if type(sec) ~= "number" or sec < 0 then return "--" end
    if sec < 60 then return string.format("%ds", sec) end
    if sec < 3600 then
        return string.format("%dm %02ds", math.floor(sec / 60), sec % 60)
    end
    local h = math.floor(sec / 3600)
    local m = math.floor((sec % 3600) / 60)
    return string.format("%dh %02dm", h, m)
end

local function TruncateMiddle(text, maxLen)
    if type(text) ~= "string" then return "" end
    if #text <= maxLen then return text end
    local half = math.floor((maxLen - 3) / 2)
    return text:sub(1, half) .. "..." .. text:sub(-half)
end

local function SeverityColor(severity)
    return SEV[severity or "unknown"] or SEV.unknown
end

local function SetText(fs, text)
    if fs and text ~= nil then fs:SetText(text) end
end

-- ============================================================
-- SECTION: Hero Card (session summary)
-- Top card: big identity line, zone, duration, event counts,
-- and a row of severity pills.
-- ============================================================
local function BuildHero(body)
    local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
    card:SetPoint("TOPLEFT",  body, "TOPLEFT",  PAD, -PAD)
    card:SetPoint("TOPRIGHT", body, "TOPRIGHT", -PAD, -PAD)
    card:SetHeight(HERO_HEIGHT)
    Back(card, UI.card, UI.border, 1, 1)

    local topEdge = Solid(card, "OVERLAY", UI.accent, 0.85)
    topEdge:SetHeight(2)
    topEdge:SetPoint("TOPLEFT",  card, "TOPLEFT",  1, -1)
    topEdge:SetPoint("TOPRIGHT", card, "TOPRIGHT", -1, -1)

    hero.card = card

    -- Left column: identity block
    local LEFT_X = 28
    local ID_Y   = 26
    hero.identity = NewFS(card, "OVERLAY", FONT.display, UI.text)
    hero.identity:SetPoint("TOPLEFT", card, "TOPLEFT", LEFT_X, -ID_Y)
    hero.identity:SetJustifyH("LEFT")
    hero.identity:SetText("...")

    hero.zone = NewFS(card, "OVERLAY", FONT.body, UI.muted)
    hero.zone:SetPoint("TOPLEFT", hero.identity, "BOTTOMLEFT", 0, -10)
    hero.zone:SetJustifyH("LEFT")
    hero.zone:SetText("")

    hero.sessionTag = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    hero.sessionTag:SetPoint("TOPLEFT", hero.zone, "BOTTOMLEFT", 0, -8)
    hero.sessionTag:SetJustifyH("LEFT")
    hero.sessionTag:SetText("")

    -- Middle column: session duration, big clean typography.
    -- Anchored at an offset LEFT of the card's horizontal center so it
    -- sits in the clear space between the identity block on the left
    -- and the severity pill row on the right rather than crowding
    -- either one. MID_X_OFFSET is negative so the anchor moves left.
    local MID_X_OFFSET = -160
    local midLabel = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    midLabel:SetPoint("TOP", card, "TOP", MID_X_OFFSET, -ID_Y - 2)
    midLabel:SetText("Session Duration")

    hero.duration = NewFS(card, "OVERLAY", FONT.huge, UI.accent)
    hero.duration:SetPoint("TOP", midLabel, "BOTTOM", 0, -10)
    hero.duration:SetText("--")

    -- Right column: severity label above the pill row.
    local pillLabel = NewFS(card, "OVERLAY", FONT.small, UI.muted)
    pillLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -28, -ID_Y - 2)
    pillLabel:SetText("Severity - Active Incidents")

    -- Pills chain leftward with comfortable gap between each.
    local pillW, pillH = 100, 36
    local pillGap = 10
    local prev = nil
    for i, sev in ipairs(SEVERITY_ORDER) do
        local pill = CreateFrame("Frame", nil, card, "BackdropTemplate")
        pill:SetSize(pillW, pillH)
        if i == 1 then
            pill:SetPoint("TOPRIGHT", pillLabel, "BOTTOMRIGHT", 0, -10)
        else
            pill:SetPoint("RIGHT", prev, "LEFT", -pillGap, 0)
        end
        local color = SEV[sev]
        Back(pill, color, UI.border, 0.18, 0.6)

        local lbl = NewFS(pill, "OVERLAY", FONT.tiny, color)
        lbl:SetPoint("TOPLEFT", pill, "TOPLEFT", 10, -7)
        lbl:SetText(SEVERITY_LABEL[sev] or sev)

        local count = NewFS(pill, "OVERLAY", FONT.heading, UI.text)
        count:SetPoint("BOTTOMRIGHT", pill, "BOTTOMRIGHT", -10, 4)
        count:SetText("0")

        hero.pills[sev] = { frame = pill, count = count }
        prev = pill
    end

    -- The header strip at the top of the window already shows session,
    -- events, incidents, and archive counts. Duplicating them here
    -- created visual collision with the divider to the cards below, so
    -- the hero now lets that bottom area breathe.
end

-- ============================================================
-- SECTION: Top Incidents Card (bottom-left)
-- ============================================================
local TOP_ROW_H   = 44          -- row height including its own padding
local TOP_ROW_GAP = 10          -- vertical gap between rows
local TOP_ROWS    = 5           -- top-5 at-a-glance; fuller lists live in Stream
local TOP_ROW_INSET_X = 18      -- horizontal inset from card edge

local function BuildTopRow(parent, index)
    -- Button (not Frame) so clicks register. Clicking a row jumps
    -- to the Stream tab and selects that incident in its inspector.
    local row = CreateFrame("Button", nil, parent)
    local yOffset = CARD_CONTENT_Y + (index - 1) * (TOP_ROW_H + TOP_ROW_GAP)
    row:SetPoint("TOPLEFT",  parent, "TOPLEFT",  TOP_ROW_INSET_X, -yOffset)
    row:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -TOP_ROW_INSET_X, -yOffset)
    row:SetHeight(TOP_ROW_H)
    row:EnableMouse(true)

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

    row:SetScript("OnEnter", function(self)
        self.bg:SetVertexColor(UI.rowHover[1], UI.rowHover[2], UI.rowHover[3], 1)
    end)
    row:SetScript("OnLeave", function(self)
        self.bg:SetVertexColor(UI.inset[1], UI.inset[2], UI.inset[3], 1)
    end)
    row:SetScript("OnClick", function(self)
        if not self.fp then return end
        local shell  = _G.MidnightUI_Console_UI
        local stream = _G.MidnightUI_Console_Stream
        if shell and shell.SetActiveTab then
            shell.SetActiveTab("stream")
        end
        if stream and stream.SelectIncident then
            stream.SelectIncident(self.fp)
        end
    end)

    -- Severity accent bar on the left edge
    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

    -- Count pill
    local pill = CreateFrame("Frame", nil, row, "BackdropTemplate")
    pill:SetSize(72, 26)
    pill:SetPoint("LEFT", row, "LEFT", 18, 0)
    Back(pill, UI.bg, UI.border, 1, 0.8)
    local pillText = NewFS(pill, "OVERLAY", FONT.body, UI.accent)
    pillText:SetPoint("CENTER")
    pillText:SetText("x0")
    row.pillText = pillText

    -- Message body — comfortable gap from the pill; reserves room
    -- on the right for the source tag.
    local msg = NewFS(row, "OVERLAY", FONT.body, UI.text)
    msg:SetPoint("LEFT", pill, "RIGHT", 16, 0)
    msg:SetJustifyH("LEFT")
    msg:SetPoint("RIGHT", row, "RIGHT", -150, 0)
    msg:SetText("")
    row.msg = msg

    -- Source tag on the right
    local src = NewFS(row, "OVERLAY", FONT.small, UI.muted)
    src:SetPoint("RIGHT", row, "RIGHT", -14, 0)
    src:SetText("")
    row.src = src

    row:Hide()
    return row
end

local function BuildTopIncidents(body)
    -- Compute from LAYOUT (deterministic) rather than body:GetWidth()
    -- (which can return 0 before the first layout pass).
    local bodyW = LAYOUT.width - 2
    local col1Right = math.floor(bodyW / 2 - GAP / 2)
    local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
    card:SetPoint("TOPLEFT",  body, "TOPLEFT",  PAD, -(PAD + HERO_HEIGHT + GAP))
    card:SetPoint("BOTTOMLEFT", body, "BOTTOMLEFT", PAD, PAD)
    card:SetPoint("RIGHT", body, "LEFT", col1Right, 0)
    Back(card, UI.panel, UI.border, 1, 1)

    -- No top-edge accent on the list cards: only the hero gets the
    -- cyan signature to keep a clean visual hierarchy and avoid the
    -- two bottom cards reading as a second divider below the hero.

    local title = NewFS(card, "OVERLAY", FONT.heading, UI.text)
    title:SetPoint("TOPLEFT", card, "TOPLEFT", TOP_ROW_INSET_X, -CARD_TITLE_Y)
    title:SetText("Top Incidents This Session")

    top.card = card

    for i = 1, TOP_ROWS do
        top.rows[i] = BuildTopRow(card, i)
    end

    local empty = NewFS(card, "OVERLAY", FONT.body, UI.muted)
    empty:SetPoint("CENTER", card, "CENTER", 0, -10)
    empty:SetText("No Incidents Captured Yet")
    top.empty = empty
end

-- ============================================================
-- SECTION: Addon Health Grid (bottom-right)
-- ============================================================
local TILE_W        = 188
local TILE_H        = 74
local TILE_GAP_X    = 14
local TILE_GAP_Y    = 12
local GRID_INSET_X  = 20
local TILES_PER_ROW = 3

local function BuildAddonTile(parent, col, row_idx)
    local tile = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    tile:SetSize(TILE_W, TILE_H)
    local x = GRID_INSET_X + (col - 1) * (TILE_W + TILE_GAP_X)
    local y = CARD_CONTENT_Y + (row_idx - 1) * (TILE_H + TILE_GAP_Y)
    tile:SetPoint("TOPLEFT", parent, "TOPLEFT", x, -y)
    Back(tile, UI.inset, UI.border, 1, 0.8)

    -- Top-edge accent (severity-colored)
    local topEdge = Solid(tile, "OVERLAY", SEV.info, 0.85)
    topEdge:SetHeight(2)
    topEdge:SetPoint("TOPLEFT",  tile, "TOPLEFT",  1, -1)
    topEdge:SetPoint("TOPRIGHT", tile, "TOPRIGHT", -1, -1)
    tile.topEdge = topEdge

    local name = NewFS(tile, "OVERLAY", FONT.body, UI.text)
    name:SetPoint("TOPLEFT", tile, "TOPLEFT", 14, -14)
    name:SetText("")
    tile.name = name

    local stats = NewFS(tile, "OVERLAY", FONT.small, UI.muted)
    stats:SetPoint("BOTTOMLEFT", tile, "BOTTOMLEFT", 14, 12)
    stats:SetText("")
    tile.stats = stats

    tile:Hide()
    return tile
end

local function BuildAddonGrid(body)
    local bodyW = LAYOUT.width - 2
    local col1Right = math.floor(bodyW / 2 - GAP / 2)
    local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
    card:SetPoint("TOPRIGHT", body, "TOPRIGHT", -PAD, -(PAD + HERO_HEIGHT + GAP))
    card:SetPoint("BOTTOMRIGHT", body, "BOTTOMRIGHT", -PAD, PAD)
    card:SetPoint("LEFT", body, "LEFT", col1Right + GAP, 0)
    Back(card, UI.panel, UI.border, 1, 1)

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

    grid.card = card

    -- Pre-build 5 rows * 3 cols = 15 tile slots. Taller tiles fit
    -- comfortably below the title block without overflowing the card.
    for r = 1, 5 do
        for c = 1, TILES_PER_ROW do
            grid.tiles[#grid.tiles + 1] = BuildAddonTile(card, c, r)
        end
    end

    local empty = NewFS(card, "OVERLAY", FONT.body, UI.muted)
    empty:SetPoint("CENTER", card, "CENTER", 0, -10)
    empty:SetText("No Addon Activity Yet")
    grid.empty = empty
end

-- ============================================================
-- SECTION: Build Entry Point
-- ============================================================
local _bodyFrame = nil
local _ticker    = nil

local function BuildOverview(body)
    _bodyFrame = body
    BuildHero(body)
    BuildTopIncidents(body)
    BuildAddonGrid(body)
end

-- ============================================================
-- SECTION: Refresh
-- Reads engine state and updates everything built above.
-- ============================================================
local function RefreshHero()
    local engine = Engine()
    if not hero.identity then return end

    local info = engine and engine.GetSessionInfo and engine.GetSessionInfo()
    local ctx = info and info.context or {}
    local session = (info and info.session) or 0
    local started = (info and info.startTime) or 0

    local identityParts = {}
    if ctx.player then identityParts[#identityParts + 1] = ctx.player end
    if ctx.spec   then identityParts[#identityParts + 1] = ctx.spec end
    if ctx.class  then
        local ui = _G.MidnightUI_Console_UI
        identityParts[#identityParts + 1] = (ui and ui.FormatClassTag and ui.FormatClassTag(ctx.class)) or ctx.class
    end
    local identityText = (#identityParts > 0) and table.concat(identityParts, "  -  ") or "Unknown character"
    SetText(hero.identity, identityText)

    local zoneParts = {}
    if ctx.zone then zoneParts[#zoneParts + 1] = ctx.zone end
    if ctx.subzone and ctx.subzone ~= ctx.zone then zoneParts[#zoneParts + 1] = ctx.subzone end
    local zoneText = (#zoneParts > 0) and table.concat(zoneParts, "  -  ") or "unknown location"
    SetText(hero.zone, zoneText)

    SetText(hero.sessionTag, string.format("SESSION #%d", session))

    local now = (time and time()) or 0
    local dur = (started > 0) and (now - started) or 0
    SetText(hero.duration, FormatDuration(dur))

    local sevCounts = engine and engine.GetSeverityCounts and engine.GetSeverityCounts() or {}
    for sev, widget in pairs(hero.pills) do
        local n = sevCounts[sev] or 0
        SetText(widget.count, tostring(n))
    end
end

local function RefreshTopIncidents()
    local engine = Engine()
    if not top.card then return end

    local list = engine and engine.GetTopIncidents and engine.GetTopIncidents(TOP_ROWS) or {}

    if #list == 0 then
        top.empty:Show()
        for _, row in ipairs(top.rows) do row:Hide() end
        return
    end
    top.empty:Hide()

    for i, row in ipairs(top.rows) do
        local e = list[i]
        if e then
            row.fp = e.fingerprint
            local color = SeverityColor(e.severity)
            row.sevBar:SetVertexColor(color[1], color[2], color[3], 1)
            SetText(row.pillText, "x" .. tostring(e.count or 1))
            SetText(row.msg, TruncateMiddle(tostring(e.message or ""), 72))
            local srcLabel = e.source or e.addon or "?"
            SetText(row.src, tostring(srcLabel))
            row:Show()
        else
            row.fp = nil
            row:Hide()
        end
    end
end

local function HealthColorForAddon(a)
    -- green if worst is info, amber if medium, red if high/critical
    local sev = a.worstSeverity or "info"
    if sev == "critical" or sev == "high" then
        return SEV.critical
    elseif sev == "medium" then
        return SEV.medium
    else
        return {0.35, 0.85, 0.55}   -- green for healthy
    end
end

local function RefreshAddonGrid()
    local engine = Engine()
    if not grid.card then return end

    local list = engine and engine.GetAddonSummary and engine.GetAddonSummary() or {}

    if #list == 0 then
        grid.empty:Show()
        for _, tile in ipairs(grid.tiles) do tile:Hide() end
        return
    end
    grid.empty:Hide()

    local maxTiles = #grid.tiles
    for i = 1, maxTiles do
        local a = list[i]
        local tile = grid.tiles[i]
        if a then
            local color = HealthColorForAddon(a)
            tile.topEdge:SetVertexColor(color[1], color[2], color[3], 0.85)
            SetText(tile.name, TruncateMiddle(tostring(a.addon or "?"), 22))
            SetText(tile.stats, string.format(
                "%d events  |  %d incidents",
                a.eventCount or 0, a.incidentCount or 0
            ))
            tile:Show()
        else
            tile:Hide()
        end
    end
end

local function RefreshOverview()
    pcall(RefreshHero)
    pcall(RefreshTopIncidents)
    pcall(RefreshAddonGrid)
end

-- ============================================================
-- SECTION: Live Ticker
-- Session Duration ticks up by the second while the Overview
-- tab is visible. Guarded so only one ticker ever runs, and
-- the step skips refresh entirely when the body is hidden so
-- we don't waste work on an unseen tab.
-- ============================================================
local function TickerStep()
    if not _bodyFrame then return end
    if not _bodyFrame:IsShown() then return end
    RefreshOverview()
end

local function EnsureTicker()
    if _ticker then return end
    if C_Timer and C_Timer.NewTicker then
        _ticker = C_Timer.NewTicker(REFRESH_INTERVAL, TickerStep)
    end
end

-- ============================================================
-- SECTION: Registration
-- ============================================================
SHELL.RegisterTabContent("overview", BuildOverview, function()
    EnsureTicker()
    RefreshOverview()
end)

-- End of Console_UI_Overview.lua
