-- VDS_Log
-- ============================================================================
-- Structured logging with trace tags. Minimal engine-default surface;
-- see HDGR's Log for a full implementation (ring buffer, trace toggling).

VDS = VDS or {}
VDS.Log = VDS.Log or {}

local Log = VDS.Log

Log._activeTraces = Log._activeTraces or {}
Log._debug = false

-- Ring buffer of recent log lines (raw text, no color codes, for the popup
-- log window). Filled by all print-emitting log methods so the user can open
-- the window after the fact and see backlog.
Log._buffer = Log._buffer or {}
Log._bufferMax = 2000

local function bufferPush(line)
    -- Prepend HH:MM:SS so log readers can correlate entries to chat / Blizzard
    -- event timing without enabling timestamps in chat itself. date() is a
    -- boundary call (Blizzard global); falls through to "??:??:??" if absent
    -- (test env). Applies uniformly to Debug/Info/Warn/Error/Success/Trace
    -- since all level methods funnel through this single push point.
    local stamped = (_G.date and ("[" .. _G.date("%H:%M:%S") .. "] ") or "[??:??:??] ")
                 .. line
    local b = Log._buffer
    b[#b + 1] = stamped
    if #b > Log._bufferMax then
        table.remove(b, 1)
    end
    if Log._window and Log._window:IsShown() then  -- exception(nullable): window built lazily on first /vds log
        Log:_AppendToWindow(stamped)
    end
end

function Log:SetDebug(on) self._debug = on and true or false end
function Log:IsDebug()    return self._debug end

function Log:IsTraceActive(tag) return self._activeTraces[tag] == true end

function Log:Trace(tag, msg)
    if self._activeTraces[tag] then
        local line = ("[VDS:%s] %s"):format(tag, tostring(msg))
        print(("|cffffb060[VDS:%s]|r %s"):format(tag, tostring(msg)))
        bufferPush(line)
    end
end

function Log:Warn(tag, msg)
    local line = ("[VDS:%s WARN] %s"):format(tag, tostring(msg))
    print(("|cffff5555[VDS:%s]|r %s"):format(tag, tostring(msg)))
    bufferPush(line)
end

-- Error: always visible (debug flag irrelevant). MUST exist on the minimal surface;
-- ErrorBoundary/Boot/layout hooks call it, and its absence causes secondary errors.
function Log:Error(tag, msg)
    local line = ("[VDS:%s ERROR] %s"):format(tag, tostring(msg))
    print(("|cffff2020[VDS:%s ERROR]|r %s"):format(tag, tostring(msg)))
    bufferPush(line)
end

-- Success: unconditional green print for lifecycle milestones (migration, hard reset).
-- Distinct from Warn ("needs attention"); these signal a completed state change.
function Log:Success(tag, msg)
    local line = ("[VDS:%s] %s"):format(tag, tostring(msg))
    print(("|cff40d860[VDS:%s]|r %s"):format(tag, tostring(msg)))
    bufferPush(line)
end

function Log:Info(tag, msg)
    if self._debug then
        local line = ("[VDS:%s] %s"):format(tag, tostring(msg))
        print(("|cff90b4ff[VDS:%s]|r %s"):format(tag, tostring(msg)))
        bufferPush(line)
    end
end

-- Debug: buffer-only (visible via /vds log). Skips chat to avoid noise.
function Log:Debug(tag, msg)
    if self._debug then
        bufferPush(("[VDS:%s] %s"):format(tag, tostring(msg)))
    end
end

function Log:ToggleTrace(tag)
    self._activeTraces[tag] = not self._activeTraces[tag] or nil
    return self._activeTraces[tag] == true
end

-- ============================================================================
-- Popup log window
-- ============================================================================
-- Scrollable multiline EditBox via InputScrollFrameTemplate (Blizzard's macro
-- editor uses it). Diagnostic tool only -- no theme integration.
-- Click in the text, Ctrl+A to select all, Ctrl+C to copy.

function Log:_AppendToWindow(line)
    local w = self._window
    if not w or not w.editBox then return end
    local existing = w.editBox:GetText() or ""
    if existing == "" then
        w.editBox:SetText(line)
    else
        w.editBox:SetText(existing .. "\n" .. line)
    end
    -- Auto-scroll to bottom on append.
    local scroll = w.inputScroll and w.inputScroll.ScrollFrame  -- exception(boundary): InputScrollFrameTemplate internals vary by client
    if scroll then scroll:SetVerticalScroll(scroll:GetVerticalScrollRange()) end
end

local function buildWindow()
    local f = CreateFrame("Frame", "VDSLogWindow", _G.UIParent, "BackdropTemplate")
    f:SetSize(720, 460)
    f:SetPoint("CENTER")
    f:SetFrameStrata("DIALOG")
    f:SetMovable(true)
    f:SetResizable(true)
    f:SetResizeBounds(400, 240)
    f:SetClampedToScreen(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)

    f:SetBackdrop({
        bgFile   = "Interface/Tooltips/UI-Tooltip-Background",
        edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
        tile     = true, tileSize = 16, edgeSize = 16,
        insets   = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    f:SetBackdropColor(0.05, 0.05, 0.05, 0.95)

    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOP", 0, -8)
    title:SetText("VDS Log")

    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", 0, 0)
    close:SetScript("OnClick", function() f:Hide() end)

    local hint = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hint:SetPoint("TOPLEFT", 12, -28)
    hint:SetText("Click in the text area, Ctrl+A to select all, Ctrl+C to copy.")

    local clear = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    clear:SetSize(60, 20)
    clear:SetPoint("TOPRIGHT", -28, -24)
    clear:SetText("Clear")
    clear:SetScript("OnClick", function()
        wipe(Log._buffer)
        if f.editBox then f.editBox:SetText("") end
    end)

    -- Footer block: friendly message + Discord URL (read-only, highlight on
    -- focus). Sits at the bottom so the scroll area shrinks to make room.
    local FOOTER_H = 42  -- 18 EditBox + 4 gap + 14 message + 6 padding
    local DISCORD_URL = "https://discord.gg/RWZaxJaHFP"

    local footerMsg = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    footerMsg:SetPoint("BOTTOMLEFT",  12, 28 + 18 + 4)
    footerMsg:SetPoint("BOTTOMRIGHT", -32, 28 + 18 + 4)
    footerMsg:SetJustifyH("LEFT")
    footerMsg:SetText("Hey there! Vamoose would like to see this -- pop into Discord:")

    local discord = CreateFrame("EditBox", nil, f, "BackdropTemplate")
    discord:SetPoint("BOTTOMLEFT",  12, 28)
    discord:SetPoint("BOTTOMRIGHT", -32, 28)
    discord:SetHeight(18)
    discord:SetBackdrop({
        bgFile   = "Interface/Tooltips/UI-Tooltip-Background",
        edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
        tile     = true, tileSize = 8, edgeSize = 8,
        insets   = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    discord:SetBackdropColor(0.08, 0.08, 0.14, 0.95)
    discord:SetFontObject("ChatFontSmall")
    discord:SetAutoFocus(false)
    discord:SetTextInsets(6, 6, 0, 0)
    discord:SetText(DISCORD_URL)
    discord:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
    discord:SetScript("OnEscapePressed",   function(self) self:ClearFocus()    end)
    discord:SetScript("OnEnterPressed",    function(self) self:ClearFocus()    end)
    -- Read-only: revert any user edit.
    discord:SetScript("OnTextChanged", function(self, userInput)
        if userInput then self:SetText(DISCORD_URL) end
    end)

    -- InputScrollFrameTemplate: parentKey "EditBox" + parentKey "ScrollFrame".
    -- Its EditBox is multi-line by default. Provides the scrollbar and a
    -- character counter (CharCount) we hide.
    local input = CreateFrame("ScrollFrame", nil, f, "InputScrollFrameTemplate")
    input:SetPoint("TOPLEFT",     12, -50)
    input:SetPoint("BOTTOMRIGHT", -32, 28 + FOOTER_H)
    input.EditBox:SetFontObject("ChatFontSmall")
    input.EditBox:SetWidth(input:GetWidth() - 18)
    input.EditBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    if input.CharCount then input.CharCount:Hide() end  -- exception(boundary): InputScrollFrameTemplate internals vary by client
    f.inputScroll = input
    f.editBox     = input.EditBox

    local grip = CreateFrame("Button", nil, f)
    grip:SetSize(16, 16)
    grip:SetPoint("BOTTOMRIGHT", -4, 4)
    grip:SetNormalTexture("Interface/ChatFrame/UI-ChatIM-SizeGrabber-Up")
    grip:SetHighlightTexture("Interface/ChatFrame/UI-ChatIM-SizeGrabber-Highlight")
    grip:SetPushedTexture("Interface/ChatFrame/UI-ChatIM-SizeGrabber-Down")
    grip:SetScript("OnMouseDown", function() f:StartSizing("BOTTOMRIGHT") end)
    grip:SetScript("OnMouseUp", function()
        f:StopMovingOrSizing()
        input.EditBox:SetWidth(input:GetWidth() - 18)
    end)

    f:Hide()
    return f
end

function Log:OpenWindow()
    if not self._window then
        self._window = buildWindow()
    end
    self._window.editBox:SetText(table.concat(self._buffer, "\n"))
    self._window:Show()
    _G.C_Timer.After(0, function()
        local scroll = self._window.inputScroll and self._window.inputScroll.ScrollFrame  -- exception(boundary): InputScrollFrameTemplate internals vary by client
        if scroll then scroll:SetVerticalScroll(scroll:GetVerticalScrollRange()) end
    end)
end

function Log:CloseWindow()
    if self._window then self._window:Hide() end
end
