local addonName, TOAST = ...

-- Local-only observability for the Crew State beta.  It reads the live
-- backend state but never sends anything until the explicit refresh button
-- is clicked.

local frame
local rows = {}
local traceFrame
local traceRows = {}
local tracePage = 1
local TRACE_ROWS_PER_PAGE = 16

local TEAL = { 0, 0.92, 0.79 }
local GOLD = { 0.88, 0.75, 0.36 }
local MUTED = { 0.65, 0.65, 0.65 }

local function Age(timestamp)
    timestamp = tonumber(timestamp)
    if not timestamp or timestamp <= 0 then return "never" end
    local seconds = math.max(0, GetServerTime() - timestamp)
    if seconds < 60 then return "now" end
    if seconds < 3600 then return math.floor(seconds / 60) .. "m ago" end
    return math.floor(seconds / 3600) .. "h ago"
end

local function Remaining(timestamp)
    local seconds = math.max(0, (tonumber(timestamp) or 0) - GetServerTime())
    if seconds < 60 then return seconds .. "s" end
    return math.floor(seconds / 60) .. "m"
end

local function BridgeState(state)
    local phase = state.bridgePhase
    if phase == "backoff" then
        return "backoff, retry in " .. Remaining(state.bridgeBackoffUntil)
    elseif phase == "awaiting_ack" then
        return (state.bridgeTarget or "?") .. " (awaiting ACK)"
    elseif phase == "transferring" then
        return (state.bridgeTarget or "?") .. " (transferring)"
    elseif phase then
        return "selecting broker"
    end
    local success = tonumber(state.bridgeLastSuccessAt)
    if success and GetServerTime() - success < 15 * 60 then
        return "synced " .. Age(success) .. " (cooldown)"
    end
    local nextAttempt = tonumber(state.bridgeNextAttemptAt)
    if nextAttempt and GetServerTime() < nextAttempt then
        return "brokers exhausted, retry in " .. Remaining(nextAttempt)
    end
    return "idle"
end

local function SetRow(index, label, value, color)
    local row = rows[index]
    if not row then return end
    row.label:SetText(label)
    row.value:SetText(value)
    color = color or { 1, 1, 1 }
    row.value:SetTextColor(color[1], color[2], color[3], 1)
end

local function Refresh()
    if not frame or not TOAST.GetCrewCommsStatus then return end
    local state = TOAST.GetCrewCommsStatus()
    local bridge = state.bridgeBlockedReason or BridgeState(state)
    local transfers = string.format("%d incoming / %d outgoing / %d expected",
        state.incomingTransfers, state.outgoingTransfers, state.expectedPackages)
    local cacheSync = string.format("attempt %s  |  success %s",
        Age(state.lastCacheSyncAttemptAt), Age(state.lastCacheSyncSuccessAt))

    local rosterIdentity = not state.rosterKnown and "not found in TOAST roster"
        or state.tideIdentity and "found | TIDE/CPT"
        or "found | no TIDE/CPT tag"
    SetRow(1, "Roster identity", rosterIdentity, state.tideIdentity and TEAL or GOLD)
    local transport
    if state.guildTransport then
        transport = "Tide ready"
    elseif state.guildName and state.guildName:lower() == "tide" then
        transport = "Tide, roster external"
    elseif state.guildName then
        transport = state.guildName .. " (not Tide)"
    else
        transport = "not in a guild"
    end
    SetRow(2, "Guild transport", transport, state.guildTransport and TEAL or GOLD)
    SetRow(3, "Community", state.inCommunity and "known" or "not readable yet",
        state.inCommunity and TEAL or GOLD)
    SetRow(4, "Outbound queue", tostring(state.queuePending) .. " pending")
    SetRow(5, "Broker bridge", bridge,
        (state.bridgeBlockedReason or state.bridgePhase) and GOLD or MUTED)
    SetRow(6, "Transfers", transfers)
    SetRow(7, "Claims / harvest", string.format("%d claims  |  %d targets", state.activeClaims, state.harvestTargets))
    SetRow(8, "Guild state request", Age(state.lastGuildStateRequestAt))
    SetRow(9, "Cache sync", cacheSync)
    SetRow(10, "Key cache", string.format("%d known keys  |  week %s", state.knownKeys,
        state.keyCacheWeekId or "unknown"))
    SetRow(11, "Synced cache week", state.lastCacheSyncWeekId or "never")
end

local function Shorten(value, limit)
    value = tostring(value or "-")
    if #value <= limit then return value end
    return value:sub(1, limit - 3) .. "..."
end

local function RefreshTrace()
    if not traceFrame or not traceFrame:IsShown() then return end
    local entries = TOAST.GetCommsTrace and TOAST.GetCommsTrace() or {}
    local pages = math.max(1, math.ceil(#entries / TRACE_ROWS_PER_PAGE))
    tracePage = math.max(1, math.min(tracePage, pages))

    local newest = #entries - (tracePage - 1) * TRACE_ROWS_PER_PAGE
    local oldest = math.max(1, newest - TRACE_ROWS_PER_PAGE + 1)
    local rowIndex = 1
    for entryIndex = oldest, newest do
        local entry = entries[entryIndex]
        local row = traceRows[rowIndex]
        if entry and row then
            row.time:SetText(entry.time)
            row.direction:SetText(entry.direction)
            row.channel:SetText(entry.channel)
            row.type:SetText(entry.type)
            row.peer:SetText(Shorten(entry.peer, 25))
            row.result:SetText(Shorten(entry.result, 16))
            local color = entry.direction == "IN" and TEAL or entry.direction == "LOCAL" and MUTED or GOLD
            row.direction:SetTextColor(color[1], color[2], color[3])
            row:Show()
        end
        rowIndex = rowIndex + 1
    end
    for index = rowIndex, #traceRows do traceRows[index]:Hide() end

    traceFrame.pageFS:SetText(string.format("Page %d/%d  -  %d local events", tracePage, pages, #entries))
end

TOAST.RefreshCommsTrace = RefreshTrace

local function BuildTrace()
    traceFrame = CreateFrame("Frame", "TOASTCommsTrace", UIParent, "BackdropTemplate")
    Mixin(traceFrame, BackdropTemplateMixin)
    traceFrame:SetSize(700, 500)
    traceFrame:SetPoint("CENTER", UIParent, "CENTER", 24, -14)
    traceFrame:SetFrameStrata("DIALOG")
    traceFrame:SetMovable(true)
    traceFrame:EnableMouse(true)
    traceFrame:RegisterForDrag("LeftButton")
    traceFrame:SetScript("OnDragStart", traceFrame.StartMoving)
    traceFrame:SetScript("OnDragStop", traceFrame.StopMovingOrSizing)
    traceFrame:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
        edgeSize = 26,
        insets = { left = 9, right = 9, top = 9, bottom = 9 },
    })
    traceFrame:SetBackdropColor(0.03, 0.05, 0.07, 0.98)
    traceFrame:SetBackdropBorderColor(TEAL[1], TEAL[2], TEAL[3], 0.65)
    traceFrame:Hide()

    local title = traceFrame:CreateFontString(nil, "OVERLAY", "TOASTFontNormalHuge")
    title:SetPoint("TOPLEFT", traceFrame, "TOPLEFT", 22, -20)
    title:SetText("TOAST Comms Trace")
    title:SetTextColor(TEAL[1], TEAL[2], TEAL[3])

    local subtitle = traceFrame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlightSmall")
    subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -5)
    subtitle:SetText("Last 50 local events - metadata only, cleared on /reload")
    subtitle:SetTextColor(MUTED[1], MUTED[2], MUTED[3])

    local divider = traceFrame:CreateTexture(nil, "ARTWORK")
    divider:SetColorTexture(1, 1, 1, 0.12)
    divider:SetPoint("TOPLEFT", traceFrame, "TOPLEFT", 20, -70)
    divider:SetPoint("TOPRIGHT", traceFrame, "TOPRIGHT", -20, -70)
    divider:SetHeight(1)

    local headers = {
        { "TIME", 24, 52, "LEFT" }, { "DIR", 82, 34, "LEFT" }, { "CHANNEL", 120, 66, "LEFT" },
        { "TYPE", 192, 142, "LEFT" }, { "PEER", 340, 174, "LEFT" }, { "RESULT", -24, 112, "RIGHT" },
    }
    for _, header in ipairs(headers) do
        local label = traceFrame:CreateFontString(nil, "OVERLAY", "TOASTFontNormalSmall")
        if header[4] == "RIGHT" then
            label:SetPoint("TOPRIGHT", traceFrame, "TOPRIGHT", header[2], -82)
        else
            label:SetPoint("TOPLEFT", traceFrame, "TOPLEFT", header[2], -82)
        end
        label:SetWidth(header[3])
        label:SetJustifyH(header[4])
        label:SetText(header[1])
        label:SetTextColor(MUTED[1], MUTED[2], MUTED[3])
    end

    for index = 1, TRACE_ROWS_PER_PAGE do
        local y = -100 - (index - 1) * 21
        local row = CreateFrame("Frame", nil, traceFrame)
        row:SetSize(652, 20)
        row:SetPoint("TOPLEFT", traceFrame, "TOPLEFT", 24, y)
        local line = row:CreateTexture(nil, "BACKGROUND")
        line:SetAllPoints()
        line:SetColorTexture(1, 1, 1, index % 2 == 0 and 0.025 or 0.01)

        local function Cell(point, width, justify)
            local cell = row:CreateFontString(nil, "OVERLAY", "TOASTFontHighlightSmall")
            if justify == "RIGHT" then
                cell:SetPoint("RIGHT", row, "RIGHT", point, 0)
            else
                cell:SetPoint("LEFT", row, "LEFT", point, 0)
            end
            cell:SetWidth(width)
            cell:SetJustifyH(justify or "LEFT")
            cell:SetWordWrap(false)
            return cell
        end
        row.time = Cell(0, 52)
        row.direction = Cell(58, 34)
        row.channel = Cell(96, 66)
        row.type = Cell(168, 142)
        row.peer = Cell(316, 174)
        row.result = Cell(0, 112, "RIGHT")
        traceRows[index] = row
    end

    local older = CreateFrame("Button", nil, traceFrame, "UIPanelButtonTemplate")
    older:SetSize(74, 24)
    older:SetPoint("BOTTOMLEFT", traceFrame, "BOTTOMLEFT", 22, 20)
    older:SetText("Older")
    older:SetScript("OnClick", function() tracePage = tracePage + 1; RefreshTrace() end)

    local newer = CreateFrame("Button", nil, traceFrame, "UIPanelButtonTemplate")
    newer:SetSize(74, 24)
    newer:SetPoint("LEFT", older, "RIGHT", 6, 0)
    newer:SetText("Newer")
    newer:SetScript("OnClick", function() tracePage = tracePage - 1; RefreshTrace() end)

    local clear = CreateFrame("Button", nil, traceFrame, "UIPanelButtonTemplate")
    clear:SetSize(74, 24)
    clear:SetPoint("LEFT", newer, "RIGHT", 6, 0)
    clear:SetText("Clear")
    clear:SetScript("OnClick", function()
        if TOAST.ClearCommsTrace then TOAST.ClearCommsTrace() end
        tracePage = 1
        RefreshTrace()
    end)

    traceFrame.pageFS = traceFrame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlightSmall")
    traceFrame.pageFS:SetPoint("BOTTOM", traceFrame, "BOTTOM", 0, 27)
    traceFrame.pageFS:SetTextColor(MUTED[1], MUTED[2], MUTED[3])

    local close = CreateFrame("Button", nil, traceFrame, "UIPanelButtonTemplate")
    close:SetSize(100, 24)
    close:SetPoint("BOTTOMRIGHT", traceFrame, "BOTTOMRIGHT", -22, 20)
    close:SetText("Close")
    close:SetScript("OnClick", function() traceFrame:Hide() end)

    traceFrame:SetScript("OnShow", RefreshTrace)
end

function TOAST.ShowCommsTrace()
    if not traceFrame then BuildTrace() end
    tracePage = 1
    traceFrame:Show()
    traceFrame:Raise()
end

local function Build()
    frame = CreateFrame("Frame", "TOASTCrewCommsDebug", UIParent, "BackdropTemplate")
    frame:SetSize(500, 405)
    frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    frame:SetFrameStrata("DIALOG")
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
    frame:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
        edgeSize = 26,
        insets = { left = 9, right = 9, top = 9, bottom = 9 },
    })
    frame:SetBackdropColor(0.03, 0.05, 0.07, 0.98)
    frame:SetBackdropBorderColor(TEAL[1], TEAL[2], TEAL[3], 0.65)
    frame:Hide()

    local title = frame:CreateFontString(nil, "OVERLAY", "TOASTFontNormalHuge")
    title:SetPoint("TOPLEFT", frame, "TOPLEFT", 22, -20)
    title:SetText("Crew State Comms")
    title:SetTextColor(TEAL[1], TEAL[2], TEAL[3])

    local subtitle = frame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlight")
    subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
    subtitle:SetText("Local protocol diagnostics - no automatic sends")
    subtitle:SetTextColor(MUTED[1], MUTED[2], MUTED[3])

    local divider = frame:CreateTexture(nil, "ARTWORK")
    divider:SetColorTexture(1, 1, 1, 0.12)
    divider:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -70)
    divider:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -20, -70)
    divider:SetHeight(1)

    for index = 1, 11 do
        local y = -84 - (index - 1) * 23
        local row = {}
        row.label = frame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlight")
        row.label:SetPoint("TOPLEFT", frame, "TOPLEFT", 24, y)
        row.label:SetWidth(145)
        row.label:SetJustifyH("LEFT")
        row.label:SetTextColor(MUTED[1], MUTED[2], MUTED[3])

        row.value = frame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlight")
        row.value:SetPoint("TOPLEFT", frame, "TOPLEFT", 175, y)
        row.value:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -24, y)
        row.value:SetJustifyH("RIGHT")
        rows[index] = row
    end

    local refresh = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    refresh:SetSize(160, 24)
    refresh:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 22, 20)
    refresh:SetText("Refresh actions")
    refresh:SetScript("OnClick", function()
        if TOAST.RefreshUnknowns then TOAST.RefreshUnknowns() end
        Refresh()
    end)

    local trace = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    trace:SetSize(110, 24)
    trace:SetPoint("BOTTOMLEFT", refresh, "BOTTOMRIGHT", 8, 0)
    trace:SetText("View trace")
    trace:SetScript("OnClick", function()
        if TOAST.ShowCommsTrace then TOAST.ShowCommsTrace() end
    end)

    local close = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    close:SetSize(100, 24)
    close:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -22, 20)
    close:SetText("Close")
    close:SetScript("OnClick", function() frame:Hide() end)

    frame:SetScript("OnShow", Refresh)
end

function TOAST.ToggleCommsDebug()
    if not frame then Build() end
    if frame:IsShown() then frame:Hide() else frame:Show() end
end
