-- modules/loot/LootTracker.lua
-- Persistent floating loot roll tracker for BiS items.
-- One card per active roll; each card shows icon, player rolls, and result.

EbonholdHub.LootTracker = {}

local UI   = EbonholdHub.UI
local C    = UI.Colors
local FLAT = "Interface\\Buttons\\WHITE8X8"

-- ── Layout constants ──────────────────────────────────────────────────────────
local FRAME_W   = 340
local HEADER_H  = 28
local ICON_SZ   = 50
local PAD       = 10
local ACCENT_W  = 3
local ROW_H     = 18
local BADGE_W   = 42
local BADGE_H   = 13
local MAX_ROWS  = 5
local FOOTER_H  = 22
local CARD_GAP  = 4
local DONE_TTL  = 6

-- Vertical anchors (from card TOPLEFT, negative = down)
local ICON_Y  = -PAD                              -- icon top
local DIV_Y   = -(PAD + ICON_SZ + PAD)           -- divider line  = -70
local ROW0_Y  = DIV_Y - 1 - 4                    -- first row     = -75

local QCOLOR = {
    [0] = {0.62,0.62,0.62}, [1] = {1.00,1.00,1.00},
    [2] = {0.12,1.00,0.00}, [3] = {0.00,0.44,0.87},
    [4] = {0.64,0.21,0.93}, [5] = {1.00,0.50,0.00},
}

local tracker  -- singleton container
local cards = {}   -- rollID → cardData
local order = {}   -- display order

-- ── Card height ────────────────────────────────────────────────────────────────
local function CardH(numRolls)
    local rows = math.max(1, math.min(numRolls, MAX_ROWS))
    -- icon-area + divider + gap + rows + gap + footer
    return (-DIV_Y) + 1 + 4 + rows * ROW_H + 4 + FOOTER_H
end

-- ── Singleton container (header bar only) ────────────────────────────────────
local function GetTracker()
    if tracker then return tracker end

    local f = CreateFrame("Frame", "EbonholdHubLootTracker", UIParent)
    f:SetWidth(FRAME_W)
    f:SetHeight(HEADER_H)
    f:SetPoint("RIGHT", UIParent, "RIGHT", -80, 0)
    f:SetFrameStrata("HIGH")
    f:SetMovable(true)
    f:EnableMouse(true)
    f:SetClampedToScreen(true)
    UI.Fill(f, C.bg)
    UI.AddThinBorder(f, C.border, 1)

    -- Left accent strip (full height, grows with cards)
    local accentL = f:CreateTexture(nil, "ARTWORK")
    accentL:SetTexture(FLAT); accentL:SetVertexColor(unpack(C.accent))
    accentL:SetPoint("TOPLEFT",    f, "TOPLEFT",    0, 0)
    accentL:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 0)
    accentL:SetWidth(ACCENT_W)
    f._accent = accentL

    -- Top accent line (2 px)
    local accentT = f:CreateTexture(nil, "ARTWORK")
    accentT:SetTexture(FLAT); accentT:SetVertexColor(unpack(C.accent))
    accentT:SetPoint("TOPLEFT",  f, "TOPLEFT",  0, 0)
    accentT:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
    accentT:SetHeight(2)

    -- Header bar (right of accent strip)
    local hdr = CreateFrame("Frame", nil, f)
    hdr:SetPoint("TOPLEFT",  accentL, "TOPRIGHT",  0,  0)
    hdr:SetPoint("TOPRIGHT", f,       "TOPRIGHT",  0,  0)
    hdr:SetHeight(HEADER_H)
    hdr:SetFrameLevel(f:GetFrameLevel() + 2)
    UI.Fill(hdr, C.bgHeader)
    hdr:EnableMouse(true)
    hdr:SetScript("OnMouseDown", function() f:StartMoving() end)
    hdr:SetScript("OnMouseUp",   function() f:StopMovingOrSizing() end)

    -- Header bottom separator
    local sep = hdr:CreateTexture(nil, "ARTWORK")
    sep:SetTexture(FLAT); sep:SetVertexColor(unpack(C.border))
    sep:SetPoint("BOTTOMLEFT",  hdr, "BOTTOMLEFT",  0, 0)
    sep:SetPoint("BOTTOMRIGHT", hdr, "BOTTOMRIGHT", 0, 0)
    sep:SetHeight(1)

    -- Dice icon
    local dice = hdr:CreateTexture(nil, "OVERLAY")
    dice:SetSize(13, 13)
    dice:SetPoint("LEFT", hdr, "LEFT", 8, 0)
    dice:SetTexture("Interface\\Icons\\INV_Misc_Dice_02")
    dice:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    -- Title
    local title = hdr:CreateFontString(nil, "OVERLAY")
    UI.SetFont(title, UI.FONT_SIZE_NORMAL, C.text)
    title:SetPoint("LEFT", dice, "RIGHT", 6, 0)
    title:SetText("BiS Loot Roll")

    -- Close button
    local closeBtn = CreateFrame("Button", nil, hdr)
    closeBtn:SetSize(18, 18)
    closeBtn:SetPoint("RIGHT", hdr, "RIGHT", -4, 0)
    local closeX = closeBtn:CreateFontString(nil, "OVERLAY")
    UI.SetFont(closeX, 13, C.textDim)
    closeX:SetPoint("CENTER")
    closeX:SetText("X")
    closeBtn:SetScript("OnEnter", function() UI.SetFont(closeX, 13, C.danger) end)
    closeBtn:SetScript("OnLeave", function() UI.SetFont(closeX, 13, C.textDim) end)
    closeBtn:SetScript("OnClick", function()
        for i = #order, 1, -1 do
            EbonholdHub.LootTracker.RemoveRoll(order[i])
        end
    end)

    f:Hide()
    tracker = f
    return f
end

-- ── Relayout all cards under the header ──────────────────────────────────────
local function Relayout()
    local f = GetTracker()
    if #order == 0 then f:Hide(); return end

    local yOff = -HEADER_H
    for _, rid in ipairs(order) do
        local cd = cards[rid]
        if cd and cd.frame then
            local h = CardH(#cd.rolls)
            cd.frame:SetHeight(h)
            cd.frame:ClearAllPoints()
            cd.frame:SetPoint("TOPLEFT",  f, "TOPLEFT",  0, yOff)
            cd.frame:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, yOff)
            yOff = yOff - h - CARD_GAP
        end
    end
    f:SetHeight(-yOff + CARD_GAP)
    f:Show()
end

-- ── Update roll row sub-frames ────────────────────────────────────────────────
local function RefreshRows(cd)
    for _, slot in ipairs(cd.rowSlots) do slot:Hide() end

    local n      = #cd.rolls
    local start  = math.max(1, n - MAX_ROWS + 1)
    local myName = UnitName and UnitName("player") or ""
    local r      = 1

    -- Determine winner's position in the list (to star their row)
    local winnerIdx = nil
    if cd.resolved and cd.winner then
        for i = start, n do
            if cd.rolls[i].player == cd.winner then
                winnerIdx = i; break
            end
        end
    end

    for i = start, n do
        local e    = cd.rolls[i]
        local slot = cd.rowSlots[r]
        if not slot then break end

        -- Roll type color and label
        local tr, tg, tb, label
        if e.rt == "Need" then
            tr, tg, tb, label = 0.30, 0.90, 0.50, "NEED"
        elseif e.rt == "Greed" then
            tr, tg, tb, label = 1.00, 0.78, 0.00, "GREED"
        else
            tr, tg, tb, label = 0.50, 0.50, 0.55, "PASS"
        end

        -- Badge background and label
        slot._badgeBg:SetVertexColor(tr * 0.20, tg * 0.20, tb * 0.20, 1)
        slot._badgeFS:SetTextColor(tr, tg, tb)
        slot._badgeFS:SetText(label)

        -- Thin badge border tinted by type
        slot._badgeBorder:SetVertexColor(tr * 0.55, tg * 0.55, tb * 0.55, 1)

        -- Row bg: subtle type tint for Need/Greed, none for Pass
        if e.rt ~= "Pass" then
            slot._rowBg:SetVertexColor(tr * 0.04, tg * 0.04, tb * 0.04, 1)
        else
            slot._rowBg:SetVertexColor(0, 0, 0, 0)
        end

        -- Player name
        local isMe      = (e.player == myName)
        local isWinner  = (i == winnerIdx)
        if isWinner then
            slot._nameFS:SetTextColor(0.35, 0.95, 0.55)
        elseif isMe then
            slot._nameFS:SetTextColor(1.00, 0.95, 0.55)
        else
            slot._nameFS:SetTextColor(unpack(C.text))
        end
        slot._nameFS:SetText(e.player)

        -- Roll number
        if e.val then
            if isWinner then
                slot._numFS:SetTextColor(0.35, 0.95, 0.55)
                slot._numFS:SetText(tostring(e.val) .. " >>")
            elseif isMe then
                slot._numFS:SetTextColor(1.00, 0.95, 0.55)
                slot._numFS:SetText(tostring(e.val))
            else
                slot._numFS:SetTextColor(0.78, 0.78, 0.82)
                slot._numFS:SetText(tostring(e.val))
            end
        else
            slot._numFS:SetTextColor(0.35, 0.35, 0.40)
            slot._numFS:SetText("—")
        end

        slot:Show()
        r = r + 1
    end
end

-- ── Create one roll-row sub-frame ─────────────────────────────────────────────
local function MakeRowSlot(parent, idx)
    local slot = CreateFrame("Frame", nil, parent)
    slot:SetPoint("TOPLEFT",  parent, "TOPLEFT",  ACCENT_W + 2, ROW0_Y - (idx - 1) * ROW_H)
    slot:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -2, ROW0_Y - (idx - 1) * ROW_H)
    slot:SetHeight(ROW_H - 2)
    slot:SetFrameLevel(parent:GetFrameLevel() + 2)

    -- Row tinted background
    local rowBg = slot:CreateTexture(nil, "BACKGROUND")
    rowBg:SetTexture(FLAT); rowBg:SetAllPoints(); rowBg:SetVertexColor(0, 0, 0, 0)
    slot._rowBg = rowBg

    -- Type badge frame
    local badge = CreateFrame("Frame", nil, slot)
    badge:SetSize(BADGE_W, BADGE_H)
    badge:SetPoint("LEFT", slot, "LEFT", 4, 0)
    badge:SetFrameLevel(slot:GetFrameLevel() + 1)

    local badgeBg = badge:CreateTexture(nil, "BACKGROUND")
    badgeBg:SetTexture(FLAT); badgeBg:SetAllPoints()
    slot._badgeBg = badgeBg

    -- Thin border on badge (1px, tinted by type)
    local badgeBorderTex = badge:CreateTexture(nil, "BORDER")
    badgeBorderTex:SetTexture(FLAT)
    badgeBorderTex:SetAllPoints()
    -- We'll set size via overlay later; just store ref
    slot._badgeBorder = badgeBorderTex

    local badgeFS = badge:CreateFontString(nil, "OVERLAY")
    badgeFS:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    badgeFS:SetPoint("CENTER")
    slot._badgeFS = badgeFS

    -- Player name
    local nameFS = slot:CreateFontString(nil, "OVERLAY")
    UI.SetFont(nameFS, UI.FONT_SIZE_SMALL, C.text)
    nameFS:SetPoint("LEFT",  badge, "RIGHT", 7, 0)
    nameFS:SetPoint("RIGHT", slot,  "RIGHT", -54, 0)
    nameFS:SetJustifyH("LEFT")
    slot._nameFS = nameFS

    -- Roll number (right-aligned)
    local numFS = slot:CreateFontString(nil, "OVERLAY")
    UI.SetFont(numFS, UI.FONT_SIZE_NORMAL, C.text)
    numFS:SetPoint("RIGHT", slot, "RIGHT", -4, 0)
    numFS:SetWidth(48)
    numFS:SetJustifyH("RIGHT")
    slot._numFS = numFS

    slot:Hide()
    return slot
end

-- ── Build one card frame ──────────────────────────────────────────────────────
local function BuildCard(parent, cd)
    local card = CreateFrame("Frame", nil, parent)
    card:SetWidth(FRAME_W)
    UI.Fill(card, C.bg)
    UI.AddThinBorder(card, C.border, 1)

    local qc = QCOLOR[cd.quality] or QCOLOR[1]

    -- Left quality-colored accent strip (mirrors the outer accent but quality-tinted)
    local qAccent = card:CreateTexture(nil, "ARTWORK")
    qAccent:SetTexture(FLAT); qAccent:SetVertexColor(qc[1], qc[2], qc[3], 1)
    qAccent:SetPoint("TOPLEFT",    card, "TOPLEFT",    0, 0)
    qAccent:SetPoint("BOTTOMLEFT", card, "BOTTOMLEFT", 0, 0)
    qAccent:SetWidth(ACCENT_W)

    local ICON_X  = ACCENT_W + PAD
    local ICON_CX = ICON_X + ICON_SZ / 2
    local ICON_CY = ICON_Y - ICON_SZ / 2

    -- All icon decorations in BACKGROUND — drawn before ARTWORK, never covers the icon
    -- Outer glow (capped: s/2 <= 35 so TOPLEFT.Y stays negative = inside card)
    local glows = { {ICON_SZ + 10, 0.07}, {ICON_SZ + 3, 0.18} }
    for _, g in ipairs(glows) do
        local s   = g[1]
        local glt = card:CreateTexture(nil, "BACKGROUND")
        glt:SetTexture(FLAT)
        glt:SetVertexColor(qc[1], qc[2], qc[3], g[2])
        glt:SetSize(s, s)
        glt:SetPoint("TOPLEFT", card, "TOPLEFT", ICON_CX - s/2, ICON_CY + s/2)
    end

    -- Quality-colored 1px border ring (BACKGROUND, so always behind the icon)
    local iconRing = card:CreateTexture(nil, "BACKGROUND")
    iconRing:SetTexture(FLAT)
    iconRing:SetVertexColor(qc[1], qc[2], qc[3], 0.85)
    iconRing:SetSize(ICON_SZ + 2, ICON_SZ + 2)
    iconRing:SetPoint("TOPLEFT", card, "TOPLEFT", ICON_X - 1, ICON_Y + 1)

    -- Item icon (ARTWORK — always rendered in front of BACKGROUND)
    local icon = card:CreateTexture(nil, "ARTWORK")
    icon:SetSize(ICON_SZ, ICON_SZ)
    icon:SetPoint("TOPLEFT", card, "TOPLEFT", ICON_X, ICON_Y)
    icon:SetTexture(cd.iconPath or "Interface\\Icons\\INV_Misc_QuestionMark")
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    local TEXT_X = ICON_X + ICON_SZ + PAD

    -- Item name (title size, quality-colored)
    local nameFS = card:CreateFontString(nil, "OVERLAY")
    UI.SetFont(nameFS, UI.FONT_SIZE_TITLE, qc)
    nameFS:SetPoint("TOPLEFT",  card, "TOPLEFT",  TEXT_X, ICON_Y)
    nameFS:SetPoint("TOPRIGHT", card, "TOPRIGHT", -PAD, 0)
    nameFS:SetJustifyH("LEFT")
    nameFS:SetText(cd.name or "Unknown Item")

    -- Slot + set info
    local slotFS = card:CreateFontString(nil, "OVERLAY")
    UI.SetFont(slotFS, UI.FONT_SIZE_SMALL, C.textDim)
    slotFS:SetPoint("TOPLEFT",  card, "TOPLEFT",  TEXT_X, ICON_Y - 17)
    slotFS:SetPoint("TOPRIGHT", card, "TOPRIGHT", -PAD, 0)
    slotFS:SetJustifyH("LEFT")
    slotFS:SetText((cd.slotLabel or "?") .. "  |cff2a3050(" .. (cd.setName or "BiS") .. ")|r")

    -- Auto-Need status line
    local needFS = card:CreateFontString(nil, "OVERLAY")
    UI.SetFont(needFS, UI.FONT_SIZE_SMALL, C.textDim)
    needFS:SetPoint("TOPLEFT", card, "TOPLEFT", TEXT_X, ICON_Y - 31)

    local function RefreshNeed()
        if cd.autoNeeded == true then
            needFS:SetTextColor(0.30, 0.90, 0.50)
            needFS:SetText(">> Auto-Need rolled")
        elseif cd.autoNeeded == false then
            needFS:SetTextColor(unpack(C.textDim))
            needFS:SetText("Auto-Need: off")
        else
            needFS:SetTextColor(0.80, 0.80, 0.20)
            needFS:SetText("waiting...")
        end
    end
    RefreshNeed()
    cd.refreshNeed = RefreshNeed

    -- Divider below icon area
    local div = card:CreateTexture(nil, "ARTWORK")
    div:SetTexture(FLAT); div:SetVertexColor(unpack(C.border)); div:SetHeight(1)
    div:SetPoint("TOPLEFT",  card, "TOPLEFT",  ACCENT_W, DIV_Y)
    div:SetPoint("TOPRIGHT", card, "TOPRIGHT", 0, DIV_Y)

    -- Roll row sub-frames (pool of MAX_ROWS)
    local rowSlots = {}
    for i = 1, MAX_ROWS do
        rowSlots[i] = MakeRowSlot(card, i)
    end
    cd.rowSlots = rowSlots

    -- ── Footer bar (status) ───────────────────────────────────────────────────
    local footer = CreateFrame("Frame", nil, card)
    footer:SetPoint("BOTTOMLEFT",  card, "BOTTOMLEFT",  0, 0)
    footer:SetPoint("BOTTOMRIGHT", card, "BOTTOMRIGHT", 0, 0)
    footer:SetHeight(FOOTER_H)
    footer:SetFrameLevel(card:GetFrameLevel() + 3)
    UI.Fill(footer, C.elementBg)

    local footSep = footer:CreateTexture(nil, "ARTWORK")
    footSep:SetTexture(FLAT); footSep:SetVertexColor(unpack(C.border)); footSep:SetHeight(1)
    footSep:SetPoint("TOPLEFT",  footer, "TOPLEFT",  0, 0)
    footSep:SetPoint("TOPRIGHT", footer, "TOPRIGHT", 0, 0)

    -- Bottom border for footer (card border is covered by footer child frame)
    local footerBottom = footer:CreateTexture(nil, "ARTWORK")
    footerBottom:SetTexture(FLAT); footerBottom:SetVertexColor(unpack(C.border))
    footerBottom:SetPoint("BOTTOMLEFT",  footer, "BOTTOMLEFT",  0, 0)
    footerBottom:SetPoint("BOTTOMRIGHT", footer, "BOTTOMRIGHT", 0, 0)
    footerBottom:SetHeight(1)

    -- Status text (right side of footer) — ASCII only, WoW font has no unicode bullets
    local statusFS = footer:CreateFontString(nil, "OVERLAY")
    UI.SetFont(statusFS, UI.FONT_SIZE_SMALL, C.accent)
    statusFS:SetPoint("RIGHT", footer, "RIGHT", -PAD, 0)
    statusFS:SetJustifyH("RIGHT")
    statusFS:SetText("Rolling...")
    cd.statusFS = statusFS

    -- Roll count (left side of footer)
    local countFS = footer:CreateFontString(nil, "OVERLAY")
    UI.SetFont(countFS, UI.FONT_SIZE_SMALL, C.textDim)
    countFS:SetPoint("LEFT", footer, "LEFT", ACCENT_W + PAD, 0)
    cd.countFS = countFS

    -- Animate the "Rolling..." dot suffix using footer's own OnUpdate (no orphan frame)
    local dotTimer = 0
    local dotIdx   = 0
    local dotSufx  = { ".", "..", "..." }
    footer:SetScript("OnUpdate", function(self, dt)
        if cd.resolved then self:SetScript("OnUpdate", nil); return end
        dotTimer = dotTimer + dt
        if dotTimer >= 0.55 then
            dotTimer = 0
            dotIdx   = (dotIdx % 3) + 1
            statusFS:SetText("Rolling" .. dotSufx[dotIdx])
        end
    end)

    cd.frame = card
end

-- ── Chat attribution ──────────────────────────────────────────────────────────
local function GetNewestActiveID()
    for i = #order, 1, -1 do
        local cd = cards[order[i]]
        if cd and not cd.resolved then return order[i] end
    end
end

local function OnChatMsg(_, msg)
    -- "PlayerName rolls 87 (Need)."
    local player, val, rt = msg:match("^(.+) rolls (%d+) %((%a+)%)%.")
    if player and val and rt then
        local rid = GetNewestActiveID()
        if rid then EbonholdHub.LootTracker.AddPlayerRoll(rid, player, rt, tonumber(val)) end
        return
    end
    -- "PlayerName passes."
    local passer = msg:match("^(.+) passes%.$")
    if passer then
        local rid = GetNewestActiveID()
        if rid then EbonholdHub.LootTracker.AddPlayerRoll(rid, passer, "Pass", nil) end
        return
    end
    -- "PlayerName receives item: [Item]."  or  "receives loot:"
    local winner, rest = msg:match("^(.+) receives item: (.+)$")
    if not winner then winner, rest = msg:match("^(.+) receives loot: (.+)$") end
    if winner and rest then
        local lowerRest = rest:lower()
        for _, rid in ipairs(order) do
            local cd = cards[rid]
            if cd and not cd.resolved and cd.name then
                if lowerRest:find(cd.name:lower(), 1, true) then
                    EbonholdHub.LootTracker.ResolveRoll(rid, winner); return
                end
            end
        end
        for _, rid in ipairs(order) do
            if cards[rid] and not cards[rid].resolved then
                EbonholdHub.LootTracker.ResolveRoll(rid, winner); return
            end
        end
    end
end

-- ── Event frame ───────────────────────────────────────────────────────────────
local evf = CreateFrame("Frame")
evf._chatListening = false

local function UpdateChatListening()
    local want = #order > 0
    if want == evf._chatListening then return end
    evf._chatListening = want
    if want then
        evf:RegisterEvent("CHAT_MSG_LOOT")
        evf:RegisterEvent("CHAT_MSG_SYSTEM")
    else
        evf:UnregisterEvent("CHAT_MSG_LOOT")
        evf:UnregisterEvent("CHAT_MSG_SYSTEM")
    end
end

evf:RegisterEvent("CANCEL_LOOT_ROLL")
evf:SetScript("OnEvent", function(self, event, ...)
    if event == "CHAT_MSG_LOOT" or event == "CHAT_MSG_SYSTEM" then
        OnChatMsg(event, ...)
    elseif event == "CANCEL_LOOT_ROLL" then
        local rollID = ...
        local t = 0
        local tick = CreateFrame("Frame")
        tick:SetScript("OnUpdate", function(s, dt)
            t = t + dt
            if t >= 0.6 then
                s:SetScript("OnUpdate", nil)
                local cd = cards[rollID]
                if cd and not cd.resolved then
                    EbonholdHub.LootTracker.ResolveRoll(rollID, nil)
                end
            end
        end)
    end
end)

-- ── Public API ────────────────────────────────────────────────────────────────

function EbonholdHub.LootTracker.AddRoll(rollID, data)
    if cards[rollID] then return end
    local cd = {
        rollID    = rollID,
        name      = data.name or "Unknown Item",
        iconPath  = data.iconPath,
        quality   = data.quality or 1,
        slotLabel = data.slotLabel or "",
        setName   = data.setName or "BiS",
        autoNeeded = nil,
        rolls     = {},
        resolved  = false,
        winner    = nil,
    }
    BuildCard(GetTracker(), cd)
    cards[rollID] = cd
    table.insert(order, rollID)
    UpdateChatListening()
    Relayout()
end

function EbonholdHub.LootTracker.SetAutoNeed(rollID, rolled)
    local cd = cards[rollID]
    if not cd then return end
    cd.autoNeeded = rolled
    if cd.refreshNeed then cd.refreshNeed() end
end

function EbonholdHub.LootTracker.AddPlayerRoll(rollID, player, rt, val)
    local cd = cards[rollID]
    if not cd then return end
    table.insert(cd.rolls, { player = player, rt = rt, val = val })
    -- Update footer count
    if cd.countFS then
        local n = #cd.rolls
        cd.countFS:SetText(n == 1 and "1 roll recorded" or (n .. " rolls recorded"))
    end
    RefreshRows(cd)
    Relayout()
end

function EbonholdHub.LootTracker.ResolveRoll(rollID, winner)
    local cd = cards[rollID]
    if not cd or cd.resolved then return end
    cd.resolved = true
    cd.winner   = winner

    if cd.statusFS then
        local myName = UnitName and UnitName("player") or ""
        if winner == myName then
            cd.statusFS:SetTextColor(0.30, 0.90, 0.50)
            cd.statusFS:SetText(">> You won!")
        elseif winner and winner ~= "" then
            cd.statusFS:SetTextColor(unpack(C.danger))
            cd.statusFS:SetText("Lost to " .. tostring(winner))
        else
            cd.statusFS:SetTextColor(unpack(C.textDim))
            cd.statusFS:SetText("Roll complete")
        end
    end
    if cd.countFS then cd.countFS:SetText("") end
    RefreshRows(cd)

    local t = 0
    local tick = CreateFrame("Frame")
    tick:SetScript("OnUpdate", function(s, dt)
        t = t + dt
        if t >= DONE_TTL then
            s:SetScript("OnUpdate", nil)
            EbonholdHub.LootTracker.RemoveRoll(rollID)
        end
    end)
end

function EbonholdHub.LootTracker.RemoveRoll(rollID)
    local cd = cards[rollID]
    if not cd then return end
    if cd.frame then cd.frame:Hide(); cd.frame = nil end
    cards[rollID] = nil
    for i = #order, 1, -1 do
        if order[i] == rollID then table.remove(order, i); break end
    end
    UpdateChatListening()
    Relayout()
end

-- ── Test command: /ebh testloot ───────────────────────────────────────────────
function EbonholdHub.LootTracker.RunTest()
    local testID = 99991
    EbonholdHub.LootTracker.RemoveRoll(testID)

    EbonholdHub.LootTracker.AddRoll(testID, {
        name      = "Thunderfury, Blessed Blade",
        iconPath  = "Interface\\Icons\\INV_Sword_39",
        quality   = 5,   -- Legendary
        slotLabel = "Main Hand",
        setName   = "Phase 1 BiS",
    })
    EbonholdHub.LootTracker.SetAutoNeed(testID, true)

    local myName = (UnitName and UnitName("player")) or "You"
    local rolls  = {
        { player = myName,      rt = "Need",  val = 87 },
        { player = "Arthas",    rt = "Need",  val = 43 },
        { player = "Sylvanas",  rt = "Greed", val = 62 },
        { player = "Thrall",    rt = "Pass",  val = nil },
    }

    local t, idx = 0, 0
    local tick = CreateFrame("Frame")
    tick:SetScript("OnUpdate", function(self, dt)
        t = t + dt
        if t >= 0.75 then
            t = 0; idx = idx + 1
            if rolls[idx] then
                local e = rolls[idx]
                EbonholdHub.LootTracker.AddPlayerRoll(testID, e.player, e.rt, e.val)
            else
                self:SetScript("OnUpdate", nil)
                local t2 = 0
                local f2 = CreateFrame("Frame")
                f2:SetScript("OnUpdate", function(s, dt2)
                    t2 = t2 + dt2
                    if t2 >= 1.5 then
                        s:SetScript("OnUpdate", nil)
                        EbonholdHub.LootTracker.ResolveRoll(testID, myName)
                    end
                end)
            end
        end
    end)

    if DEFAULT_CHAT_FRAME then
        DEFAULT_CHAT_FRAME:AddMessage("|cff30bff5EbonholdHub:|r Loot tracker test started — /ebh testloot")
    end
end

function EbonholdHub.LootTracker.HandleSlash(msg)
    if msg == "testloot" then EbonholdHub.LootTracker.RunTest(); return true end
    return false
end

function EbonholdHub.LootTracker.Init() end
