-- TOG Tools — Diagnostics tab (UI) [developer tool]
-- Compact, de-duplicated view of captured Lua errors (including blocked /
-- forbidden addon-action errors) and Lua warnings. One summary line per distinct
-- message with an occurrence count and last-seen time — see Modules/Diagnostics
-- for why this is a summary, not a full BugSack-style trace.
--
-- Dev-only: Diagnostics is a Logs sub-tab (registered in addon.logCategories by
-- the engine), shown only when Settings > General > Developer Tools is on — via
-- the category's `gate`, which LogsTab honours in place of the normal
-- per-category toggle. This file merges the sub-tab Draw + help onto that table,
-- the same way GatheringLogSubTab etc. extend their engines; subKey / label /
-- configKey are set by the engine.

local _, addon = ...
local AceGUI = LibStub("AceGUI-3.0")

local BRAND = "|c" .. (addon.BrandColor or "ffFF8000")

local Diagnostics = addon.logCategories["diagnostics"]
local Tab    = Diagnostics
Tab.help = {
    title = "Diagnostics (developer tool)",
    lines = {
        "A compact, de-duplicated log of |cffff5555Lua errors|r and |cffffcc00Lua warnings|r the game raises in memory. Blocked/forbidden addon-action errors are folded in as errors (tagged \"blocked:\"/\"forbidden:\"). Repeats of the same message collapse into one row with a count, so it stays readable.",
        "This is |cffffffffnot|r a BugSack replacement — it keeps only the one-line message summary (addon, file:line, reason), never the full stack trace. Use it to see what's firing and how often; use BugSack for deep traces.",
        "|cffffd700Right-click any row|r for a menu — |cffffd700Copy|r opens a box with the row's text selected (Ctrl+C, then Escape). Handy for pasting an error somewhere for help.",
        "|cffffd700Errors|r are always captured. |cffffd700Warnings|r need scriptWarnings on. The blocked/forbidden addon-action errors need |cffffd700scriptErrors|r (\"Display Lua Errors\") on — that's the CVar that lets those events reach addons. Use the toggles below, or the console: |cffffd700/console scriptWarnings 1|r, |cffffd700/console scriptErrors 1|r.",
        "A blocked/forbidden action error appears only on an |cffffd700actual blocked call|r (the \"interface action failed because of an addon\" case). |cffffd700Test|r throws one on demand — it deliberately calls a protected function — so you can confirm capture works.",
        "Note: this is not a taint viewer — true taint propagation goes only to the on-disk taint.log, which addons can't read (no filesystem access). What's shown here is the live error/warning/blocked-action stream.",
    },
}

-- ---------------------------------------------------------------------------
-- Kind presentation
-- ---------------------------------------------------------------------------
local KIND_INFO = {
    error   = { label = "Error",   color = "ffff5555" },
    warning = { label = "Warning", color = "ffffcc00" },
}
local function kindCell(e)
    local i = KIND_INFO[e.kind]
    if not i then return tostring(e.kind or "?") end
    return "|c" .. i.color .. i.label .. "|r"
end
local function lastCell(e)
    if not e.last or e.last == 0 then return "-" end
    return date("%m/%d %H:%M:%S", e.last)
end

-- One-line, plain-text rendering of a row for the copy box (no color codes — it
-- should paste cleanly). Reconstructs the original "source: detail" line (e.g.
-- "Foo/Bar.lua:42: attempt to index nil", or "TOGTools: blocked: SomeFunc").
-- Trailing tag carries kind, occurrence count, last-seen.
local function rowCopyText(e)
    local typeName = (KIND_INFO[e.kind] and KIND_INFO[e.kind].label) or tostring(e.kind)
    local src  = (e.source and e.source ~= "-") and (e.source .. ": ") or ""
    local body = src .. (e.detail or "")
    return string.format("[%s x%d] %s  (last %s)", typeName, e.count or 1, body, lastCell(e))
end

-- Right-click context menu for a row. Built as a menu (not a direct action) so
-- more options can be added later. Cross-version: MenuUtil on Retail (11.0+),
-- EasyMenu / UIDropDownMenu on Classic.
local _rowMenu  -- reusable dropdown frame for the Classic path
local function showRowMenu(entry, anchor)
    local function copyRow()
        StaticPopup_Show("TOGTOOLS_DIAG_COPY", nil, nil, rowCopyText(entry))
    end

    if MenuUtil and MenuUtil.CreateContextMenu then
        MenuUtil.CreateContextMenu(anchor or UIParent, function(_, root)
            root:CreateTitle("TOG Tools")
            root:CreateButton("Copy", copyRow)
            -- (add more row actions here)
        end)
        return
    end

    -- Classic fallback (no MenuUtil): EasyMenu via a reusable dropdown frame.
    _rowMenu = _rowMenu or CreateFrame("Frame", "TOGToolsDiagRowMenu", UIParent, "UIDropDownMenuTemplate")
    local items = {
        { text = "TOG Tools", isTitle = true, notCheckable = true },
        { text = "Copy", notCheckable = true, func = copyRow },
        -- (add more row actions here)
    }
    if EasyMenu then EasyMenu(items, _rowMenu, "cursor", 0, 0, "MENU") end
end

StaticPopupDialogs["TOGTOOLS_DIAG_CLEAR"] = {
    text         = "Clear the entire Diagnostics log (errors + warnings)?",
    button1      = "Clear",
    button2      = "Cancel",
    OnAccept     = function()
        Diagnostics:ClearAll()
    end,
    timeout      = 0,
    whileDead    = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

-- Copy dialog. WoW has no clipboard API, so "copy" = drop the row's text into a
-- focused, pre-selected EditBox and let the user Ctrl+C. The text is passed as
-- the StaticPopup `data` arg and applied in OnShow. EditBox field name varies by
-- flavour (editBox vs EditBox), so feature-detect.
StaticPopupDialogs["TOGTOOLS_DIAG_COPY"] = {
    text         = "Press Ctrl+C to copy, then Escape:",
    button1      = "Close",
    hasEditBox   = true,
    editBoxWidth = 360,
    OnShow = function(self, data)
        local eb = self.editBox or self.EditBox
        if eb then
            eb:SetText(tostring(data or ""))
            eb:SetCursorPosition(0)
            eb:HighlightText()
            eb:SetFocus()
        end
    end,
    EditBoxOnEscapePressed = function(eb) eb:GetParent():Hide() end,
    EditBoxOnEnterPressed  = function(eb) eb:GetParent():Hide() end,
    timeout      = 0,
    whileDead    = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

-- ---------------------------------------------------------------------------
-- Info / summary line
-- ---------------------------------------------------------------------------
function Tab:_renderInfo()
    local lbl = self._info
    if not lbl or not lbl.SetText then return end
    local c = Diagnostics:Counts()
    lbl:SetText(string.format(
        "%sCaptured|r  •  |cffff5555%d errors|r  •  |cffffcc00%d warnings|r  •  |cffaaaaaa%d total (deduped)|r",
        BRAND, c.error or 0, c.warning or 0, c.total or 0))
end

-- ---------------------------------------------------------------------------
-- Table refresh
-- ---------------------------------------------------------------------------
function Tab:_render()
    local rl = self._rl
    if not rl then return end
    rl:SetData(Diagnostics:GetEntries())
    self:_renderInfo()
end

-- ---------------------------------------------------------------------------
-- Draw
-- ---------------------------------------------------------------------------
function Tab:Draw(container)
    container:SetLayout("Flow")
    local self2 = self

    -- Controls collapse into one List group above the full-height table body
    -- (same shape as ItemDBTab — a loose full-height child otherwise draws on
    -- top of the row directly above it).
    local controls = AceGUI:Create("SimpleGroup")
    controls:SetFullWidth(true)
    controls:SetLayout("List")
    container:AddChild(controls)

    -- ---- Search row (the EditBox is taller than the checkboxes, so it gets its
    -- own row — mixing the two heights in one Flow row is what made the controls
    -- overlap). ---------------------------------------------------------------
    local filterRow = AceGUI:Create("SimpleGroup")
    filterRow:SetFullWidth(true)
    filterRow:SetLayout("Flow")
    controls:AddChild(filterRow)

    -- Free-text search across every column (Type / Source / Detail). Replaces
    -- the old "Show" dropdown — filtering to Errors / Warnings is now the Type
    -- column-header menu. Live; filters the table in place via the RowList.
    local search = AceGUI:Create("EditBox")
    search:SetWidth(280)
    search:DisableButton(true)
    search:SetText(self._search or "")
    search:SetCallback("OnTextChanged", function(_, _, text)
        self2._search = text
        if self2._rl then self2._rl:SetSearch(text) end
    end)
    addon.UI.StyleSearchBox(search, true)
    filterRow:AddChild(search)

    -- ---- Capture toggles + clear (uniform-height widgets, one row) -------
    local toggleRow = AceGUI:Create("SimpleGroup")
    toggleRow:SetFullWidth(true)
    toggleRow:SetLayout("Flow")
    controls:AddChild(toggleRow)

    local warnChk = AceGUI:Create("CheckBox")
    warnChk:SetLabel("Capture warnings")
    warnChk:SetWidth(160)
    warnChk:SetValue(Diagnostics:IsWarningCaptureOn())
    addon.UI.AttachTooltip(warnChk, addon.UI.Brand("Capture warnings"),
        "Turn on Lua warning capture by enabling the scriptWarnings CVar. Warnings only fire while this is on. If it won't stick, run |cffffd700/console scriptWarnings 1|r.")
    warnChk:SetCallback("OnValueChanged", function(widget, _, val)
        Diagnostics:SetWarningCapture(val)
        widget:SetValue(Diagnostics:IsWarningCaptureOn())  -- reflect the real CVar state
    end)
    toggleRow:AddChild(warnChk)

    local scriptErrChk = AceGUI:Create("CheckBox")
    scriptErrChk:SetLabel("Display Lua errors")
    scriptErrChk:SetWidth(165)
    scriptErrChk:SetValue(Diagnostics:IsScriptErrorsOn())
    addon.UI.AttachTooltip(scriptErrChk, addon.UI.Brand("Display Lua errors (scriptErrors)"),
        "Toggles Blizzard's |cffffd700scriptErrors|r CVar — the built-in \"Display Lua Errors\" popup. On = |cffffd700/console scriptErrors 1|r, off = 0. TOG Tools already captures plain Lua errors without this; what it adds is Blizzard's error frame AND the gate that lets |cffffd700blocked / forbidden addon-action|r errors reach addons (we log those as Errors). |cffff9999Heads up:|r it brings back the default error popup and can fight BugSack. (This is not \"taint\" — true taint propagation isn't readable by addons.)")
    scriptErrChk:SetCallback("OnValueChanged", function(widget, _, val)
        Diagnostics:SetScriptErrors(val)
        widget:SetValue(Diagnostics:IsScriptErrorsOn())
    end)
    toggleRow:AddChild(scriptErrChk)

    local alertChk = AceGUI:Create("CheckBox")
    alertChk:SetLabel("Alert on new errors")
    alertChk:SetWidth(170)
    alertChk:SetValue(Diagnostics:IsAlertOn())
    addon.UI.AttachTooltip(alertChk, addon.UI.Brand("Alert on new errors"),
        "Pop a brief on-screen toast when a new, not-yet-seen error is captured (like BugSack). Click the toast to jump here. Repeats of an error you've already seen don't re-alert.")
    alertChk:SetCallback("OnValueChanged", function(_, _, val)
        Diagnostics:SetAlert(val)
    end)
    toggleRow:AddChild(alertChk)

    local testBtn = AceGUI:Create("Button")
    testBtn:SetText("Test")
    testBtn:SetWidth(70)
    testBtn:SetCallback("OnClick", function()
        Diagnostics:FireTestError()
    end)
    addon.UI.AttachTooltip(testBtn, addon.UI.Brand("Test"),
        "Throws |cffff5555real|r errors on command — one per capture path — to verify the tab, toast, and Titan counter: a genuine |cffffd700Lua error|r (the handler path) and a |cffffd700forbidden protected call|r (the ADDON_ACTION event path). Both also show in BugSack. Intentional and harmless — Clear afterwards.")
    toggleRow:AddChild(testBtn)

    local clearBtn = AceGUI:Create("Button")
    clearBtn:SetText("Clear")
    clearBtn:SetWidth(90)
    clearBtn:SetCallback("OnClick", function() StaticPopup_Show("TOGTOOLS_DIAG_CLEAR") end)
    addon.UI.AttachTooltip(clearBtn, addon.UI.Brand("Clear"),
        "Wipe the captured log. Capture continues; this only deletes what's stored. Asks for confirmation first.")
    toggleRow:AddChild(clearBtn)

    -- ---- Summary line ---------------------------------------------------
    local info = AceGUI:Create("Label")
    info:SetFullWidth(true)
    info:SetText(" ")
    controls:AddChild(info)
    self._info = info

    -- ---- Table body (final child — fills remaining height) --------------
    local body = AceGUI:Create("SimpleGroup")
    body:SetFullWidth(true)
    body:SetFullHeight(true)
    body:SetLayout("List")
    container:AddChild(body)

    local rl = addon.RowList:New(body.content, {
        rowHeight = 16,
        -- Right-click a row → context menu (extensible). "Copy" opens a Ctrl+C
        -- box (addons have no clipboard-write API). Anchored to the row.
        onRowClick = function(entry, _, _, button, rowFrame)
            if button == "RightButton" and entry then
                showRowMenu(entry, rowFrame)
            end
        end,
        columns = {
            { key = "kind", header = "Type", autoFit = true, minWidth = 60, justify = "LEFT",
              format = kindCell, sortKey = "kind", filterable = true,
              headerTip = "Type",
              headerTipDesc = "Error (Lua error, including blocked / forbidden addon actions) or Warning (Lua warning)." },

            { key = "source", header = "Source", autoFit = true, minWidth = 130, justify = "LEFT",
              headerTip = "Source",
              headerTipDesc = "Addon and file:line for Lua errors/warnings; the culprit addon (or macro) for blocked/forbidden actions." },

            { key = "detail", header = "Detail", justify = "LEFT",
              headerTip = "Detail",
              headerTipDesc = "The error/warning reason, or the protected function that was blocked. The stretchy column. |cffffd700Right-click any row|r for a menu — Copy opens a Ctrl+C box for pasting elsewhere." },

            { key = "count", header = "#", autoFit = true, minWidth = 34, justify = "RIGHT",
              sortDescDefault = true,
              headerTip = "Count",
              headerTipDesc = "How many times this exact message has fired. Repeats collapse into this count instead of new rows." },

            { key = "last", header = "Last seen", autoFit = true, minWidth = 110, justify = "LEFT",
              format = lastCell, sortKey = "last", sortDescDefault = true,
              headerTip = "Last seen",
              headerTipDesc = "When this message most recently fired." },
        },
    })
    rl:SetSort("last", true)   -- most-recent first
    self._rl = rl

    -- ---- Live wiring + cleanup -----------------------------------------
    -- Engine pushes here on every capture; guard against firing into a
    -- released table after the user navigates away.
    -- Only re-render when the user is actually viewing this sub-tab: the Logs
    -- top-level tab must be active AND Diagnostics must be the selected sub-tab.
    Diagnostics.onUpdate = function()
        local mw     = addon.MainWindow
        local frames = addon.DB and addon.DB.char and addon.DB.char.frames
        if mw and mw.activeTab == "logs"
           and frames and frames.logsActiveSubTab == "diagnostics" then
            self2:_render()
        end
    end

    self:_render()
    if self._search and self._search ~= "" then rl:SetSearch(self._search) end

    body:SetCallback("OnRelease", function()
        rl:Detach()
        Diagnostics.onUpdate = nil
        self2._rl   = nil
        self2._info = nil
    end)
end

function Tab:Refresh(container)
    container:ReleaseChildren()
    self:Draw(container)
end
