-- TOG Tools
-- Author: Pimptasty
-- A collection of lightweight utility tools for WoW Classic and Retail.

local addonName, addon = ...

-- ---------------------------------------------------------------------------
-- Addon global
-- ---------------------------------------------------------------------------
TOGTools       = TOGTools or {}
TOGTools.addon = addon
addon.name     = addonName

addon.BrandColor = "ffFF8000"   -- Orange, consistent with the TOG suite

local _GetAddOnMetadata = (C_AddOns and C_AddOns.GetAddOnMetadata) or GetAddOnMetadata
addon.Version = _GetAddOnMetadata(addonName, "Version") or "dev"

-- ---------------------------------------------------------------------------
-- Version flags
-- ---------------------------------------------------------------------------
local gv = {}
addon.gameVersion = gv
do
    local tocVersion = select(4, GetBuildInfo())
    gv.isVanilla = tocVersion < 20000
    gv.isTBC     = tocVersion >= 20000 and tocVersion < 30000
    gv.isWrath   = tocVersion >= 30000 and tocVersion < 40000
    gv.isCata    = tocVersion >= 40000 and tocVersion < 50000
    gv.isMists   = tocVersion >= 50000 and tocVersion < 60000
    gv.isClassic = gv.isVanilla or gv.isTBC or gv.isWrath or gv.isCata or gv.isMists
    gv.isRetail  = not gv.isClassic
    -- Retail 12.0+ uses the EventRegistry path for chat hooks.
    gv.isRetail120Plus = tocVersion >= 120000
end

-- ---------------------------------------------------------------------------
-- AceAddon
-- ---------------------------------------------------------------------------
local Ace = LibStub("AceAddon-3.0"):NewAddon(
    addonName,
    "AceConsole-3.0",
    "AceEvent-3.0",
    "AceHook-3.0"
)
addon.lib = Ace

-- ---------------------------------------------------------------------------
-- AceDB schema
-- ---------------------------------------------------------------------------
local DB_DEFAULTS = {
    char = {
        minimapPos  = 220,
        minimapHide = false,
        lastTab     = nil,         -- last-selected MainWindow tabKey
        frames = {
            mainWindow = { width = 640, height = 460 },
        },
    },
    global = {
        -- Account-wide debug flag. When true, modules emit verbose chat
        -- prints (hook install, fire-time diagnostics, etc.). Off by default.
        debug = false,
        -- Developer tools master switch. When true, modules flagged
        -- `devOnly` (e.g. the Item DB builder) get a tab in the main window;
        -- when false they're hidden entirely. Off by default so end users
        -- never see the heavyweight dev tooling. Toggled in Settings >
        -- General > Developer Tools.
        devTools = false,
        -- NamePrefix module settings — account-wide so the nickname only
        -- needs to be configured once and applies to every alt. Toons that
        -- shouldn't self-prefix rely on `hideIfCharName` (default true) to
        -- suppress the prefix when the nickname equals the character name.
        namePrefix = {
            enabled  = true,
            nickname = "",
            format   = "(%s): ",   -- %s is replaced with nickname
            say      = false,
            guild    = false,
            officer  = false,
            party    = false,
            raid     = false,
            instance = false,
            channel  = "",         -- custom channel name filter (empty = off)
            skipExclamation  = true,
            hideIfCharName   = true,
            -- Opt-in, OFF by default. When on AND GRM is installed with "show
            -- main name" active, a receive-side chat filter collapses the
            -- duplicate main name you'd otherwise see (a sender's self-added
            -- "(Main)" prefix + GRM's own "(Main):" injection). Fully inert when
            -- off — no chat filter registered, no GRM calls. See NamePrefix.lua.
            dedupeGRMMain    = false,
        },
        -- Mailbox Stale Watcher: per-alt mail snapshots, account-wide so
        -- every alt's expiring mail is visible from any character.
        mailbox = {
            enabled       = true,
            thresholdDays = 2,
            snapshots     = {},   -- [Name-Realm] = { snapshotAt, items }
        },
        -- Addon Load Monitor: passive read-only display. Only the master
        -- enable flag lives here — disabling hides the tab; the underlying
        -- data still arrives via the !TOGT companion regardless.
        addonLoad = {
            enabled = true,
        },
        -- Login Digest: one-line state-of-account summary at PLAYER_LOGIN.
        -- lastLogout is per-character (Name-Realm key) so each alt
        -- reports its own "since last seen" value.
        loginDigest = {
            enabled    = true,
            fields = {
                mail        = true,
                guildOnline = true,
                hearth      = true,
                bags        = true,
                lastLogout  = true,
            },
            lastLogout = {},   -- [Name-Realm] = serverTime
        },
        -- Logs nexus — outer tab settings shared across every sub-log
        -- (Mail / Trade / Guild as they ship). retentionDays prunes old
        -- entries on every append so SV doesn't grow unbounded.
        logs = {
            enabled       = true,
            retentionDays = 90,
        },
        -- Mail Log: every mail received (inbox takes) and sent (compose
        -- via SendMail) appended to `entries`. Per-entry `player` field
        -- supports the alt-filter dropdown so a single account-wide
        -- store covers every character.
        mailLog = {
            enabled = true,
            entries = {},   -- { ts, dir, player, other, subject, items, money, cod }
        },
        -- Trade Log: every completed player-to-player trade appended to
        -- `entries`. Slot 7 = enchant; tracked separately from the item
        -- list because it has its own visual treatment in the sub-tab.
        -- Trades inside arena/BG instances are skipped.
        tradeLog = {
            enabled = true,
            entries = {},   -- { ts, player, other, given, received, moneyGiven, moneyReceived, enchantGiven, enchantReceived, zone }
        },
        -- Guild Log: snapshots Blizzard's in-game guild event log on
        -- demand and persists entries beyond the ~100-entry cap. Keyed
        -- by `GuildName-Realm` so alts in different guilds keep
        -- independent histories. Entry shape stores Blizzard's raw
        -- (year, month, day, hour) tuple because that's the precision
        -- the API exposes — no minute/second granularity available.
        guildLog = {
            enabled = true,
            guilds  = {},   -- [GuildName-Realm] = { entries = {{ y, m, d, h, type, actor, target, rank }} }
        },
        -- Guild Bank Log: snapshots Blizzard's in-game guild bank
        -- transaction log every time the bank frame opens, and persists
        -- entries beyond Blizzard's small rolling window. Same per-guild
        -- bucket structure as guildLog. Each entry carries a stable
        -- cross-viewer id (prefix "gblog|") so future TOGBankClassic
        -- integration / cross-account sync can dedupe across players.
        guildBankLog = {
            enabled = true,
            guilds  = {},   -- [GuildName-Realm] = { entries = {{ kind, type, name, itemLink, count, tab1, tab2, y, m, d, h, ts, amount, id }} }
        },
        -- Whisper Log: every whisper sent or received — in-game and
        -- Battle.net — appended to `entries`. `isBN` separates BN from
        -- in-game; `isGM` flags Blizzard staff whispers; `lineID` is
        -- used by the engine's 5-second recent-set to dedup repeats
        -- when chat events re-fire through filter chains.
        whisperLog = {
            enabled = true,
            entries = {},   -- { ts, dir, player, other, text, isGM, isBN, guid, bnSenderID, lineID, zone }
        },
        -- Gathering Log: every node gathered (mining / herbalism / skinning
        -- / fishing) appended to `entries`. Capture is spell-cast-gated —
        -- a gather cast arms a short window and the loot that follows is
        -- attributed to it (see Modules/GatheringLog). `action` is a
        -- monotonic id grouping the multiple drops of one node so per-node
        -- yield stats are exact; `actionSeq` is the persisted counter that
        -- keeps those ids unique across sessions.
        gatheringLog = {
            enabled   = true,
            entries   = {},   -- { ts, player, prof, action, itemID, link, name, quality, expac, count, source, sourceID, continent, zone, subzone }
            actionSeq = 0,
        },
        -- Log Graphs: per-log chart preferences for the Logs > Graphs sub-tab
        -- (LibGraph-2.0 line charts). Keyed by the source log's category key
        -- (e.g. "gathering", "mail", "trade"); each holds the selected period
        -- index, a per-series visibility map, and any sub-filter value (the
        -- Gathering chart scopes series to one expansion). Plus `lastLog` =
        -- the log key last viewed in the Graphs picker. Display-only prefs —
        -- the time-series itself is derived on the fly from each log's own
        -- entries, never duplicated here.
        logGraphs = {
            lastLog = nil,
            byLog   = {},   -- [logKey] = { period, series = {[seriesKey]=bool}, sub }
        },
        -- Gratz: profile manager for auto-congratulations on events
        -- (achievements, level-ups, etc.). Each profile names a single
        -- trigger type + its criteria, message template, channel set,
        -- and cooldown. Profiles keyed by name; create/edit/delete via
        -- the Gratz tab (FGI Filters-tab pattern). Cooldown state is
        -- per-session (in-memory) — not persisted because session
        -- restarts are a fine reset point and SV bloat is the larger
        -- concern.
        gratz = {
            enabled  = true,
            profiles = {},   -- [name] = { name, enabled, trigger, ..., count }
        },
        -- Item DB (developer tool): a locally-built, searchable copy of the
        -- game's item catalog. Built by walking every item ID and recording
        -- what the server returns (see Modules/ItemDB). Bucketed by item
        -- class so type/subtype filtering stays fast. `cursor` is the resume
        -- point; `locale`/`build` stamp the capture so the tab can flag a
        -- rebuild after a language or client-version change.
        itemDB = {
            enabled  = true,
            -- Saved form is one big string per class (records sep \30, fields
            -- sep \31) — one string constant per class so the whole catalog
            -- stays well under Lua's per-chunk constant limit on load. `classes`
            -- (legacy per-item table) is kept only to migrate older saves that
            -- still loaded; the engine empties it once folded into classesBlob.
            classesBlob = {},  -- [classID] = "idStr\31name\31quality\31sub\31equip\31ilvl\31stats\30..."
            classes  = {},     -- legacy [classID] = { [idStr] = "name\31quality\31sub\31equip\31ilvl\31stats" }
            pending  = {},     -- [idStr] = true: requested but reply not yet stored (resume/gap-fill)
            count    = 0,
            cursor   = 1,      -- resume point for the forward walk
            topID    = nil,    -- set per-build from the engine default
            locale   = nil,
            build    = nil,
            complete = false,
            builtAt  = nil,
        },
        -- Smack: smack-talk lines fired by combat triggers. Each filter
        -- (FGI-Filters pattern) names one TRIGGER (HP/mana crossings, killing
        -- blow, big hit taken, combat start/end, group member down/dead, CC,
        -- death, Bloodlust), an optional `pct` threshold, a pool of `lines`
        -- (one picked at random per fire), a target-channel set, and a
        -- per-filter cooldown in MINUTES (min 1). Combat-only — nothing fires
        -- out of combat. Keyed by name like gratz; in-memory arm/cooldown
        -- state is not persisted (session restart is a fine reset point).
        smack = {
            enabled = true,
            filters = {},   -- [name] = { name, enabled, trigger, pct, lines={}, channels={}, cooldown, count }
        },
        -- Diagnostics (developer tool): a compact, de-duplicated log of Lua
        -- errors and Lua warnings captured from live handlers/events (see
        -- Modules/Diagnostics); blocked/forbidden addon-action events are folded
        -- in as errors. Errors keep only the one-line message summary, never the
        -- stack — repeats collapse into a count. Recording is gated on the
        -- Developer Tools switch, so this stays empty (and free) for end users.
        diagnostics = {
            enabled = true,
            alert   = true,   -- show the BugSack-style toast on a new distinct error
            entries = {},     -- { kind, source, detail, count, first, last }
        },
    },
}

-- ---------------------------------------------------------------------------
-- Module registry — tabs are built from this table, sorted alphabetically
-- by their `label` field.  Each module file registers itself here.
-- ---------------------------------------------------------------------------
addon.modules = {}

-- ---------------------------------------------------------------------------
-- OnInitialize / OnEnable
-- ---------------------------------------------------------------------------
function Ace:OnInitialize()
    addon.DB = LibStub("AceDB-3.0"):New("TOGTools_DB", DB_DEFAULTS, true)

    -- One-shot migration: v0.3.2 moved namePrefix from char to global.
    -- If this character has a pre-migration char.namePrefix table with a
    -- non-empty nickname AND the new global table is still at defaults
    -- (empty nickname), copy the per-character settings up. Picks whichever
    -- alt logs in first after the upgrade; subsequent alts just inherit
    -- the now-global config. Leftover char.namePrefix is harmless.
    do
        local oldCfg = rawget(addon.DB.char, "namePrefix")
        local newCfg = addon.DB.global.namePrefix
        if oldCfg and oldCfg.nickname and oldCfg.nickname ~= ""
           and (not newCfg.nickname or newCfg.nickname == "") then
            for k, v in pairs(oldCfg) do newCfg[k] = v end
        end
    end

    -- Expose Version so VersionCheck-1.0 reads it from the host object directly.
    Ace.Version = addon.Version

    -- Register with VersionCheck-1.0 on Classic; the library is not yet
    -- available on Retail so we guard with the version flag.
    if not gv.isRetail then
        local VC = LibStub("VersionCheck-1.0", true)
        if VC then
            VC:Enable(Ace)
        end
    end

end

function Ace:OnEnable()
    -- Enable all registered modules
    for _, mod in pairs(addon.modules) do
        if mod.OnEnable then mod:OnEnable() end
    end
    -- Log sub-category engines live in addon.logCategories (not addon.modules).
    -- Most self-wire via their own event frames, but any exposing OnEnable —
    -- e.g. Diagnostics, which drains the !!TOGT early buffer and attaches its
    -- live capture callback — get it called here.
    for _, cat in pairs(addon.logCategories or {}) do
        if cat.OnEnable then cat:OnEnable() end
    end
end

-- ---------------------------------------------------------------------------
-- Debug print helper — gated on `db.global.debug` (toggle in the Settings
-- panel). Use from any module for verbose diagnostics that should be silent
-- in normal play but visible when troubleshooting.
--
-- Output shape: `TOGTools [HH:MM:SS.mmm] <message>`
--
-- The timestamp comes from `date("%H:%M:%S")` (server-local wall clock) with
-- millisecond precision derived from `GetTime()`. Wall clock matches WoW's
-- own chat timestamps so you can correlate addon traces with surrounding
-- chat events; the ms suffix matters when triaging event bursts that fire
-- many times per second (e.g. GUILDBANK_UPDATE_TABS x24 on bank-open).
-- ---------------------------------------------------------------------------
function addon:Debug(msg)
    if addon.DB and addon.DB.global and addon.DB.global.debug then
        local t = (GetTime and GetTime()) or 0
        local ms = math.floor((t - math.floor(t)) * 1000)
        print(string.format("%s [%s.%03d] %s",
            addon.UI.Brand("TOGTools"),
            date("%H:%M:%S"),
            ms,
            tostring(msg)))
    end
end

-- ---------------------------------------------------------------------------
-- Module enabled-state — read via the tab module's `configKey` field, which
-- names the SV section that holds the module's settings (including its
-- `enabled` boolean). Returns true when the module/section isn't found so
-- legacy code paths and freshly registered tabs default to "on" until they
-- explicitly opt into the toggle.
-- ---------------------------------------------------------------------------
function addon:IsModuleEnabled(tabKey)
    if not (addon.DB and addon.DB.global) then return true end
    local mod = addon.modules and addon.modules[tabKey]
    if not mod then return true end
    local key = mod.configKey
    if not key then return true end
    local section = addon.DB.global[key]
    if not section then return true end
    if section.enabled == nil then return true end
    return section.enabled and true or false
end
