-- ============================================================
-- FILE: Bootstrap.lua
-- MODULE: None — global scope only, no module table
--
-- PURPOSE:
--   The very first file loaded by MidnightUI (TOC position 1).
--   Creates the global debug logging infrastructure before any
--   other module exists. Every other file in the addon calls
--   MidnightUI_Debug() to log messages; this file is what makes
--   that function available from the first line of every other file.
--
-- HOW IT WORKS:
--   All code here runs at file-scope (immediately on load).
--   1. Two global queue tables are created to hold pending log lines.
--   2. SafeToString() is defined to handle tainted/restricted values.
--   3. MidnightUI_Debug() is defined globally using an "if not" guard
--      so it cannot be accidentally overwritten by a later reload.
--   Console.lua drains MidnightUI_DebugQueue into the capture
--   engine on ADDON_LOADED, so messages logged before the engine
--   exists are preserved.
--
-- DEPENDS ON:
--   Nothing. Zero dependencies by design — it must be self-contained
--   because it loads before every other module.
--
-- EXPORTS (globals other files use):
--   MidnightUI_Debug(...)          — queue a debug log message; called
--                                    by every module in the addon
--   MidnightUI_DebugQueue          — table of pending debug strings;
--                                    drained by Console.lua
--   MidnightUI_DiagnosticsQueue    — table of pending diagnostics
--                                    entries; drained by Console.lua
--
-- OWNED SAVEDVARIABLES:
--   MidnightUISettingsPerChar      — per-character overlay/settings
--                                    table. Bootstrap seeds it from
--                                    the account-wide MidnightUISettings
--                                    on first login, then aliases
--                                    _G.MidnightUISettings to it so
--                                    every other module's reads/writes
--                                    land in the per-char table.
--
-- EVENTS REGISTERED:   None
-- FRAMES CREATED:      None
--
-- GOTCHAS:
--   • MUST remain first in MidnightUI.toc — if any file loads before
--     this one, MidnightUI_Debug() won't exist and the addon will error.
--     The per-character swap also has to run before Settings.lua etc.,
--     so load order matters for that too.
--   • SafeToString wraps tostring() in pcall because tostring() itself
--     can throw on tainted/restricted WoW values during combat.
--   • The "if not MidnightUI_Debug" guard is intentional — do not
--     remove it. It prevents a /reload from overwriting the function
--     mid-session if Bootstrap somehow loads twice.
--   • No module should capture MidnightUISettings into a file-scope
--     local (e.g. `local S = MidnightUISettings` at the top of a file).
--     The per-char swap in this file only works because every module
--     reads the global fresh inside its functions. A file-scope capture
--     would freeze a reference to the pre-swap account-wide table and
--     silently break per-character behavior.
-- ============================================================

-- ============================================================
-- SECTION: Global Queue Tables
-- Created at file-scope so they exist before any module loads.
-- MidnightUI_DebugQueue holds pending debug strings until the
-- Console capture engine is ready to ingest them.
-- MidnightUI_DiagnosticsQueue holds structured diagnostic entries.
-- Both tables use "or {}" so a /reload doesn't wipe queued messages.
--
-- GLOBALS CREATED:
--   MidnightUI_DebugQueue         — string queue for debug output
--   MidnightUI_DiagnosticsQueue   — entry queue for diagnostics panel
-- ============================================================
_G.MidnightUI_DebugQueue      = _G.MidnightUI_DebugQueue      or {}
_G.MidnightUI_DiagnosticsQueue = _G.MidnightUI_DiagnosticsQueue or {}

-- ============================================================
-- SECTION: Taint-Safe String Conversion
-- WoW can return "secret" (tainted) values during restricted
-- combat — calling tostring() on them can throw a Lua error.
-- SafeToString() wraps that call in pcall so logging never
-- crashes the addon, returning a placeholder string instead.
--
-- FUNCTIONS:
--   SafeToString(value)   — converts any value (including tainted
--                           WoW secrets) to a plain string safely;
--                           returns "[Restricted Message Hidden]"
--                           if the conversion fails
-- ============================================================
local function SafeToString(value)
    if value == nil then
        return "nil"
    end

    local ok, text = pcall(tostring, value)
    if not ok then
        return "[Restricted Message Hidden]"
    end

    local safe = "[Restricted Message Hidden]"
    if pcall(function()
        safe = table.concat({ text }, "")
    end) then
        return safe
    end

    return "[Restricted Message Hidden]"
end

-- ============================================================
-- SECTION: Global Debug Logger
-- Defines MidnightUI_Debug(), the single logging function used
-- by every module in the addon. Messages are not printed to
-- chat — they are queued in MidnightUI_DebugQueue and displayed
-- in the MidnightUI Diagnostics console panel (opened via /mui).
-- The "if not" guard prevents overwriting on /reload.
--
-- FUNCTIONS:
--   MidnightUI_Debug(...)   — accepts any number of arguments,
--                             converts each with SafeToString(),
--                             joins them with spaces, and appends
--                             the result to MidnightUI_DebugQueue
-- ============================================================
if not _G.MidnightUI_Debug then
    _G.MidnightUI_Debug = function(...)
        local count = select("#", ...)
        local parts = {}

        for i = 1, count do
            parts[i] = SafeToString(select(i, ...))
        end

        _G.MidnightUI_DebugQueue[#_G.MidnightUI_DebugQueue + 1] = table.concat(parts, " ")
    end
end

-- ============================================================
-- SECTION: Per-Character Settings Swap (+ logout sync)
-- MidnightUISettings is declared in the .toc as account-wide, and
-- MidnightUISettingsPerChar is declared as per-character. WoW
-- populates these saved globals sometime between addon files
-- loading and ADDON_LOADED firing — they are NOT reliably present
-- at file-scope. The swap therefore happens on ADDON_LOADED.
--
-- IMPORTANT GOTCHA we have to work around:
--   WoW's SavedVariables system captures the original table reference
--   for each declared SavedVariable when the addon loads. Reassigning
--   the global (e.g. `_G.MidnightUISettings = _G.MidnightUISettingsPerChar`)
--   does NOT redirect what WoW writes at logout — WoW still writes the
--   original reference, which by that point has been orphaned from
--   every runtime write. The result without a workaround: account-wide
--   SavedVariables persists nothing the user did during the session.
--
-- WORKAROUND:
--   1. Capture the original account-wide reference before any swap.
--   2. At PLAYER_LOGOUT, wipe and refill that original reference with
--      the runtime data so WoW writes the latest values.
--
-- The handler below also performs the original first-time seed and
-- the runtime alias swap (so all module reads/writes land on the
-- per-character table).
-- ============================================================

-- Capture the original account-wide reference at file-scope so we keep
-- a stable handle to it for the logout-time sync. If MidnightUISettings
-- was never saved before this is nil; we adopt the table that WoW (or
-- the swap handler) creates later.
local _muiOrigAccountWide = _G.MidnightUISettings

local function _muiDeepCopy(value)
    if type(value) ~= "table" then return value end
    local copy = {}
    for k, v in pairs(value) do
        copy[k] = _muiDeepCopy(v)
    end
    return copy
end

local _mui_perCharSwapFrame = CreateFrame("Frame")
_mui_perCharSwapFrame:RegisterEvent("ADDON_LOADED")
_mui_perCharSwapFrame:RegisterEvent("PLAYER_LOGOUT")
_mui_perCharSwapFrame:SetScript("OnEvent", function(self, event, addon)
    if event == "ADDON_LOADED" then
        if addon ~= "MidnightUI" then return end
        self:UnregisterEvent("ADDON_LOADED")

        -- If we didn't have an account-wide reference at file-scope, grab
        -- whatever WoW finished loading by ADDON_LOADED time.
        if _muiOrigAccountWide == nil then
            _muiOrigAccountWide = _G.MidnightUISettings
        end

        _G.MidnightUISettingsPerChar = _G.MidnightUISettingsPerChar or {}

        if not next(_G.MidnightUISettingsPerChar) and _G.MidnightUISettings and next(_G.MidnightUISettings) then
            _G.MidnightUISettingsPerChar = _muiDeepCopy(_G.MidnightUISettings)
        end

        _G.MidnightUISettings = _G.MidnightUISettingsPerChar
        return
    end

    if event == "PLAYER_LOGOUT" then
        -- Sync runtime data back into the original account-wide reference.
        -- WoW writes that reference (not the runtime alias) to disk.
        local runtime = _G.MidnightUISettingsPerChar
        if type(runtime) ~= "table" then return end
        if type(_muiOrigAccountWide) ~= "table" then return end
        if _muiOrigAccountWide == runtime then
            -- already aliased to the same table; nothing to sync
            return
        end
        for k in pairs(_muiOrigAccountWide) do
            _muiOrigAccountWide[k] = nil
        end
        for k, v in pairs(runtime) do
            _muiOrigAccountWide[k] = _muiDeepCopy(v)
        end
        return
    end
end)

