local addonName, addonTable = ...

local AceGUI = LibStub("AceGUI-3.0")

local TML = {}

------------------------------------------------------------
-- MODULE TABLES
------------------------------------------------------------

TML.DB = {}
TML.HELPER = {}
TML.ITEM = {}
TML.LOG = {}
TML.WHISPER = {}
TML.LOGIC = {}
TML.STYLE = {}
TML.BUILDER = {}
TML.EVENTS = {}

local ML_C = { bg = {0.10, 0.10, 0.10, 0.00} }
local ML_BACKDROP = {
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
    insets   = { left = 1, right = 1, top = 1, bottom = 1 },
}
local function MLSetBG(frame, r, g, b, a)
    frame:SetBackdrop(ML_BACKDROP)
    frame:SetBackdropColor(r, g, b, a or 1)
    frame:SetBackdropBorderColor(0, 0, 0, 0)
end
TML.API = {}

------------------------------------------------------------
-- TML.STATE / TML.UI REFS / CONSTANTS
------------------------------------------------------------

TML.STATE = {
    isEnabled = false,
    statusElapsed = 0,
}

TML.UI = {
    root = nil,

    logSection = nil,

    bopPanel = nil,
    boePanel = nil,

    bopTitle = nil,
    boeTitle = nil,

    bopCounter = nil,
    boeCounter = nil,

    bopClearButton = nil,
    boeClearButton = nil,

    bopScroll = nil,
    boeScroll = nil,

    statusPanel = nil,
    statusText = nil,
    raidLeaderText = nil,
    makeMLButton = nil,

    collectorPanel = nil,
    collectorInputs = {},

    whisperCheckBox = nil,
    whisperLabel = nil,

    toggleButton = nil,
    closeButton = nil,
}

TML.REFRESH = {
    log = nil,
    status = nil,
    controls = nil,
}

TML.TOOLTIP = CreateFrame("GameTooltip", "TMLMasterLooterScanTooltip", nil, "GameTooltipTemplate")
TML.TOOLTIP:SetOwner(UIParent, "ANCHOR_NONE")

TML.EVENT_FRAME = CreateFrame("Frame")
TML.LOADER_FRAME = CreateFrame("Frame")

TML.CONST = {
    MAX_LOG_ENTRIES = 1000,

    OUTER_LEFT = 10,
    OUTER_RIGHT = 10,
    TOP_OFFSET = -34,

    LOG_SECTION_HEIGHT = 210,
    LOG_GAP = 10,
    PANEL_INSET = 8,

    SECTION_GAP = 40,

    STATUS_HEIGHT = 110,
    STATUS_INNER_TOP_PADDING = 20,

    COLLECTOR_HEIGHT = 110,
    COLLECTOR_TITLE_GAP = 10,

    ROW_HEIGHT = 30,
    LABEL_WIDTH = 90,
    INPUT_WIDTH = 150,
    TARGET_WIDTH = 58,
    SELF_WIDTH = 48,

    FOOTER_HEIGHT = 40,

    QUALITY_NAMES = {
        [0] = "Poor",
        [1] = "Common",
        [2] = "Uncommon",
        [3] = "Rare",
        [4] = "Epic",
        [5] = "Legendary",
    },
}

------------------------------------------------------------
-- DEFAULT DATA
------------------------------------------------------------

function TML.DB.CreateEmptyStats()
    return {
        epic = 0,
        rare = 0,
        uncommon = 0,
        common = 0,
        shards = 0,
        boe = 0,
    }
end

function TML.DB.BuildDefaultSettings()
    return {
        lootCollector = UnitName("player"),
        shardsCollector = UnitName("player"),
        boeCollector = UnitName("player"),
        enabled = false,
        minQuality = 1,
        lootLog = {},
        whisperEnabled = true,
        stats = TML.DB.CreateEmptyStats(),
    }
end

------------------------------------------------------------
-- PUBLIC STATUS HELPERS
------------------------------------------------------------

function TML_IsMasterLooterEnabled()
    return TML.STATE.isEnabled
end

------------------------------------------------------------
-- SAVED VARIABLE HELPERS
------------------------------------------------------------

function TML.DB.EnsureSavedVariables()
    local defaultSettings = TML.DB.BuildDefaultSettings()

    if type(RTMasterLooterSave) ~= "table" then
        RTMasterLooterSave = CopyTable(defaultSettings)
    end

    if type(RTMasterLooterSave.lootLog) ~= "table" then
        RTMasterLooterSave.lootLog = {}
    end

    if type(RTMasterLooterSave.stats) ~= "table" then
        RTMasterLooterSave.stats = TML.DB.CreateEmptyStats()
    end

    if RTMasterLooterSave.stats.shards == nil then
        RTMasterLooterSave.stats.shards = 0
    end

    if RTMasterLooterSave.stats.boe == nil then
        RTMasterLooterSave.stats.boe = 0
    end

    if RTMasterLooterSave.stats.epic == nil then
        RTMasterLooterSave.stats.epic = 0
    end

    if RTMasterLooterSave.stats.rare == nil then
        RTMasterLooterSave.stats.rare = 0
    end

    if RTMasterLooterSave.stats.uncommon == nil then
        RTMasterLooterSave.stats.uncommon = 0
    end

    if RTMasterLooterSave.stats.common == nil then
        RTMasterLooterSave.stats.common = 0
    end

    if not RTMasterLooterSave.lootCollector or RTMasterLooterSave.lootCollector == "" then
        RTMasterLooterSave.lootCollector = UnitName("player")
    end

    if not RTMasterLooterSave.shardsCollector or RTMasterLooterSave.shardsCollector == "" then
        RTMasterLooterSave.shardsCollector = UnitName("player")
    end

    if not RTMasterLooterSave.boeCollector or RTMasterLooterSave.boeCollector == "" then
        RTMasterLooterSave.boeCollector = UnitName("player")
    end

    if RTMasterLooterSave.enabled == nil then
        RTMasterLooterSave.enabled = false
    end

    if RTMasterLooterSave.whisperEnabled == nil then
        RTMasterLooterSave.whisperEnabled = true
    end
end

------------------------------------------------------------
-- GENERAL HELPERS
------------------------------------------------------------

function TML.HELPER.GetPlayerName()
    return UnitName("player")
end

function TML.HELPER.IsInRaidGroup()
    return GetNumRaidMembers() > 0
end

function TML.HELPER.IsPlayerRaidLeader()
    if GetNumRaidMembers() == 0 then
        return false
    end

    local playerName = UnitName("player")

    for index = 1, GetNumRaidMembers() do
        local name, rank = GetRaidRosterInfo(index)

        if name == playerName then
            return rank == 2
        end
    end

    return false
end

function TML.HELPER.IsPlayerLeaderOrAssistant()
    if GetNumRaidMembers() == 0 then
        return false
    end

    local playerName = UnitName("player")

    for index = 1, GetNumRaidMembers() do
        local name, rank = GetRaidRosterInfo(index)

        if name == playerName then
            return rank == 2 or rank == 1
        end
    end

    return false
end

function TML.HELPER.IsPlayerMasterLooter()
    if not TML.HELPER.IsInRaidGroup() then
        return false
    end

    local lootMethod, masterLooterPartyID, masterLooterRaidID = GetLootMethod()

    if lootMethod ~= "master" then
        return false
    end

    return masterLooterPartyID == 0 or masterLooterRaidID == 0
end

function TML.HELPER.BuildMasterLootCandidateMap()
    local candidateMap = {}

    for index = 1, 40 do
        local candidateName = GetMasterLootCandidate(index)
        if candidateName and candidateName ~= "" then
            candidateMap[candidateName] = index
        end
    end

    return candidateMap
end

function TML.HELPER.GetSkinModule()
    if TryGetElvUISkinModule then
        local engine, skin = TryGetElvUISkinModule()
        return engine, skin
    end

    return nil, nil
end

------------------------------------------------------------
-- ITEM HELPERS
------------------------------------------------------------

function TML.ITEM.IsItemBOE(itemLink)
    if not itemLink or itemLink == "" then
        return false
    end

    TML.TOOLTIP:ClearLines()
    TML.TOOLTIP:SetOwner(UIParent, "ANCHOR_NONE")
    TML.TOOLTIP:SetHyperlink(itemLink)

    local isBOE = false

    for index = 2, 12 do
        local leftText = _G["TMLMasterLooterScanTooltipTextLeft" .. index]
        if leftText then
            local textValue = leftText:GetText()

            if textValue then
                if textValue:find("Binds when equipped") then
                    isBOE = true
                    break
                end

                if textValue:find("Binds when picked up") then
                    isBOE = false
                    break
                end
            end
        end
    end

    TML.TOOLTIP:Hide()

    return isBOE
end

function TML.ITEM.GetItemQuality(itemLink)
    local _, _, quality = GetItemInfo(itemLink)
    return quality
end

function TML.ITEM.IsIgnoredLootItem(itemLink)
    if not itemLink or itemLink == "" then
        return false
    end

    local itemName = GetItemInfo(itemLink)

    if not itemName or itemName == "" then
        local itemText = tostring(itemLink)

        if itemText:find("Emblem of Triumph") then
            return true
        end

        if itemText:find("Emblem of Heroism") then
            return true
        end

        if itemText:find("Emblem of Conquest") then
            return true
        end

        if itemText:find("Emblem of Frost") then
            return true
        end

        return false
    end

    if itemName == "Emblem of Triumph" then
        return true
    end

    if itemName == "Emblem of Heroism" then
        return true
    end

    if itemName == "Emblem of Conquest" then
        return true
    end

    if itemName == "Emblem of Frost" then
        return true
    end

    return false
end

function TML.ITEM.GetCollectorNameForItem(itemLink, quality)
    if TML.ITEM.IsItemBOE(itemLink) then
        return RTMasterLooterSave.boeCollector
    end

    if quality == 5 then
        return RTMasterLooterSave.shardsCollector
    end

    return RTMasterLooterSave.lootCollector
end

------------------------------------------------------------
-- LOG / STATS HELPERS
------------------------------------------------------------

function TML.LOG.RebuildStatsFromLootLog()
    local stats = TML.DB.CreateEmptyStats()
    local lootLog = RTMasterLooterSave.lootLog or {}

    for index = 1, #lootLog do
        local entry = lootLog[index]
        local itemLink = entry.item

        if not TML.ITEM.IsIgnoredLootItem(itemLink) then
            local quality = TML.ITEM.GetItemQuality(itemLink)

            if quality then
                if TML.ITEM.IsItemBOE(itemLink) then
                    stats.boe = stats.boe + 1
                elseif quality == 5 then
                    stats.shards = stats.shards + 1
                elseif quality == 4 then
                    stats.epic = stats.epic + 1
                elseif quality == 3 then
                    stats.rare = stats.rare + 1
                elseif quality == 2 then
                    stats.uncommon = stats.uncommon + 1
                else
                    stats.common = stats.common + 1
                end
            end
        end
    end

    RTMasterLooterSave.stats = stats
end

function TML.LOG.AddLootLogEntry(itemLink, playerName)
    local lootLog = RTMasterLooterSave.lootLog

    lootLog[#lootLog + 1] = {
        item = itemLink,
        to = playerName,
        time = date("%H:%M"),
    }

    if #lootLog > TML.CONST.MAX_LOG_ENTRIES then
        table.remove(lootLog, 1)
    end

    TML.LOG.RebuildStatsFromLootLog()
end

function TML.LOG.ClearBOPHistory()
    local oldLog = RTMasterLooterSave.lootLog or {}
    local newLog = {}

    for index = 1, #oldLog do
        local entry = oldLog[index]

        if TML.ITEM.IsItemBOE(entry.item) then
            newLog[#newLog + 1] = entry
        end
    end

    RTMasterLooterSave.lootLog = newLog
    TML.LOG.RebuildStatsFromLootLog()

    if TML.REFRESH.log then
        TML.REFRESH.log()
    end
end

function TML.LOG.ClearBOEHistory()
    local oldLog = RTMasterLooterSave.lootLog or {}
    local newLog = {}

    for index = 1, #oldLog do
        local entry = oldLog[index]

        if not TML.ITEM.IsItemBOE(entry.item) then
            newLog[#newLog + 1] = entry
        end
    end

    RTMasterLooterSave.lootLog = newLog
    TML.LOG.RebuildStatsFromLootLog()

    if TML.REFRESH.log then
        TML.REFRESH.log()
    end
end

------------------------------------------------------------
-- WHISPER HELPERS
------------------------------------------------------------

function TML.WHISPER.IsCollectorPlayer(playerName)
    if not playerName or playerName == "" then
        return false
    end

    if playerName == RTMasterLooterSave.lootCollector then
        return true
    end

    if playerName == RTMasterLooterSave.shardsCollector then
        return true
    end

    if playerName == RTMasterLooterSave.boeCollector then
        return true
    end

    return false
end

function TML.WHISPER.TryWhisperCollector(playerName, itemLink)
    if RTMasterLooterSave.whisperEnabled ~= true then
        return
    end

    if not TML.HELPER.IsPlayerMasterLooter() then
        return
    end

    if playerName == TML.HELPER.GetPlayerName() then
        return
    end

    if not TML.WHISPER.IsCollectorPlayer(playerName) then
        return
    end

    SendChatMessage(
        "RaidTools: I've sent you " .. itemLink .. ". Please check your bags.",
        "WHISPER",
        nil,
        playerName
    )
end

------------------------------------------------------------
-- LOOT MESSAGE HELPERS
------------------------------------------------------------

function TML.LOGIC.ParseLootMessage(messageText)
    local playerName, itemLink = messageText:match("([^%s]+) receives loot: (.+)%.")
    if playerName and itemLink then
        return playerName, itemLink
    end

    local receivedItemLink = messageText:match("You receive loot: (.+)%.")
    if receivedItemLink then
        return TML.HELPER.GetPlayerName(), receivedItemLink
    end

    return nil, nil
end

function TML.LOG.FormatLogLine(entry)
    local timeText = entry.time or "00:00:00"
    local itemText = entry.item or "?"
    local targetText = entry.to or "?"
    return string.format("[%s] %s -> %s", timeText, itemText, targetText)
end

------------------------------------------------------------
-- CORE LOGIC
------------------------------------------------------------

function TML.LOGIC.DistributeLoot()
    if not TML.STATE.isEnabled then
        return
    end

    if not TML.HELPER.IsInRaidGroup() then
        return
    end

    if not TML.HELPER.IsPlayerMasterLooter() then
        return
    end

    local candidateMap = TML.HELPER.BuildMasterLootCandidateMap()
    local lootItemCount = GetNumLootItems()
    local playerName = TML.HELPER.GetPlayerName()
    local selfCandidateIndex = candidateMap[playerName]

    for slotIndex = 1, lootItemCount do
        local _, _, _, quality = GetLootSlotInfo(slotIndex)
        local itemLink = GetLootSlotLink(slotIndex)

        if itemLink and quality then
            local collectorName = TML.ITEM.GetCollectorNameForItem(itemLink, quality)
            local candidateIndex = candidateMap[collectorName]

            if not candidateIndex then
                candidateIndex = selfCandidateIndex
            end

            if candidateIndex then
                GiveMasterLoot(slotIndex, candidateIndex)
            end
        end
    end
end

function TML.LOGIC.HandleLootMessage(messageText)
    if not TML.HELPER.IsInRaidGroup() then
        return
    end

    local playerName, itemLink = TML.LOGIC.ParseLootMessage(messageText)

    if not playerName or not itemLink then
        return
    end

    if itemLink:find("money:") then
        return
    end

    local quality = TML.ITEM.GetItemQuality(itemLink)
    if not quality then
        return
    end

    if TML.ITEM.IsIgnoredLootItem(itemLink) then
        return
    end

    TML.LOG.AddLootLogEntry(itemLink, playerName)

    if TML.REFRESH.log then
        TML.REFRESH.log()
    end

    TML.WHISPER.TryWhisperCollector(playerName, itemLink)

    -- Also add to Roll Manager item list (ML/RL only)
    if RM_HasPermission and RM_HasPermission() and RM_TryAddItem and RM_TryAddItem(itemLink, quality) then
        if RM_SaveItemList then RM_SaveItemList() end
        RM_skipSort = true
        if RM_RefreshItemList then RM_RefreshItemList() end
        RM_skipSort = false
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
    end
end

------------------------------------------------------------
-- TEST COMMAND
------------------------------------------------------------

SLASH_RTMLTEST1 = "/rtmltest"
SlashCmdList["RTMLTEST"] = function(messageText)
    TML.DB.EnsureSavedVariables()

    local inputText = messageText and strtrim(messageText) or ""

    if inputText == "" then
        print("Usage: /rtmltest [itemLink]")
        return
    end

    local _, itemLink, quality = GetItemInfo(inputText)

    if not quality then
        print("Item:", inputText)
        print("Quality: nil")
        print("Bind: unknown")
        print("Target: unknown")
        return
    end

    local resolvedItemLink = itemLink or inputText
    local bindType = TML.ITEM.IsItemBOE(resolvedItemLink) and "BOE" or "BOP"
    local targetName = TML.ITEM.GetCollectorNameForItem(resolvedItemLink, quality)

    print("Item:", resolvedItemLink)
    print("Quality:", TML.CONST.QUALITY_NAMES[quality] or tostring(quality))
    print("Bind:", bindType)
    print("Target:", targetName)
end

------------------------------------------------------------
-- TML.UI STYLE HELPERS
------------------------------------------------------------

function TML.STYLE.ApplyBackdrop(frame, bgR, bgG, bgB, bgA, borderR, borderG, borderB, borderA)
    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })

    frame:SetBackdropColor(bgR, bgG, bgB, bgA)
    frame:SetBackdropBorderColor(borderR, borderG, borderB, borderA)
end

function TML.STYLE.ApplyPanelStyle(frame)
    frame:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",
        edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize=12,insets={left=3,right=3,top=3,bottom=3}})
    frame:SetBackdropColor(0.10,0.10,0.10,0.97)
    frame:SetBackdropBorderColor(0.55,0.50,0.42,0.8)
end

function TML.STYLE.ApplyInputStyle(frame)
    frame:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",
        edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize=12,insets={left=3,right=3,top=3,bottom=3}})
    frame:SetBackdropColor(0.10,0.10,0.10,0.97)
    frame:SetBackdropBorderColor(0.55,0.50,0.42,0.8)
end

function TML.BUILDER.CreateSectionPanel(parent, width, height)
    local frame = CreateFrame("Frame", nil, parent)
    frame:SetSize(width, height)
    TML.STYLE.ApplyPanelStyle(frame)
    return frame
end

function TML.BUILDER.CreatePixelInput(parent, width, height)
    local background = CreateFrame("Frame", nil, parent)
    background:SetSize(width, height)
    TML.STYLE.ApplyInputStyle(background)

    local editBox = CreateFrame("EditBox", nil, background)
    editBox:SetPoint("TOPLEFT", 4, 0)
    editBox:SetPoint("BOTTOMRIGHT", -4, 0)
    editBox:SetAutoFocus(false)
    editBox:SetFontObject(ChatFontNormal)

    if editBox.Left then
        editBox.Left:Hide()
    end

    if editBox.Middle then
        editBox.Middle:Hide()
    end

    if editBox.Right then
        editBox.Right:Hide()
    end

    return background, editBox
end

function TML.STYLE.SkinButton(button, skinModule)
    -- Stock UIPanelButtonTemplate / Ace3 skin — no custom recolor
end

function TML.STYLE.SkinCheckBox(checkBox, skinModule)
    if skinModule and skinModule.HandleCheckBox then
        skinModule:HandleCheckBox(checkBox)
        return
    end
end

function TML.BUILDER.SetCollectorValue(editBox, saveKey, value)
    if not value or value == "" then
        return
    end

    editBox:SetText(value)
    RTMasterLooterSave[saveKey] = value
end

------------------------------------------------------------
-- TML.UI STATUS HELPERS
------------------------------------------------------------

function TML.REFRESH.UpdateToggleButtonText()
    if not TML.UI.toggleButton then
        return
    end

    if TML.STATE.isEnabled then
        TML.UI.toggleButton:SetText("|cff00ff00Enabled|r")
    else
        TML.UI.toggleButton:SetText("Start Master Looter")
    end
end

function TML.REFRESH.UpdateWhisperCheckBox()
    if not TML.UI.whisperCheckBox then
        return
    end

    TML.UI.whisperCheckBox:SetValue(RTMasterLooterSave.whisperEnabled == true)
end

function TML.REFRESH.UpdateCollectorInputValues()
    for saveKey, editBox in pairs(TML.UI.collectorInputs) do
        if editBox then
            editBox:SetText(RTMasterLooterSave[saveKey] or TML.HELPER.GetPlayerName())
        end
    end
end

TML.REFRESH.status = function()
    if not TML.UI.statusText or not TML.UI.raidLeaderText or not TML.UI.makeMLButton then
        return
    end

    local isInRaid = TML.HELPER.IsInRaidGroup()
    local isMasterLooter = TML.HELPER.IsPlayerMasterLooter()
    local isLeaderOrAssist = TML.HELPER.IsPlayerLeaderOrAssistant()
    local isRaidLeader = TML.HELPER.IsPlayerRaidLeader()

    if not isInRaid then
        TML.UI.statusText:SetText("|cffffff00Not In Raid|r")
        TML.UI.raidLeaderText:SetText("")
        TML.UI.makeMLButton.frame:Hide()
        return
    end

    if isMasterLooter then
        TML.UI.statusText:SetText("|cff00ff00Master Looter|r")
    else
        TML.UI.statusText:SetText("|cffff2020WARNING: Not Master Looter|r")
    end

    if isRaidLeader then
        TML.UI.raidLeaderText:SetText("|cff00ff00Raid Leader|r")
    elseif isLeaderOrAssist then
        TML.UI.raidLeaderText:SetText("|cffffcc00Assist / Can't Set ML|r")
    else
        TML.UI.raidLeaderText:SetText("|cffffaa00Not Raid Leader|r")
    end

    if isLeaderOrAssist and not isMasterLooter then
        TML.UI.makeMLButton.frame:Show()
    else
        TML.UI.makeMLButton.frame:Hide()
    end
end

TML.REFRESH.controls = function()
    TML.REFRESH.UpdateToggleButtonText()
    TML.REFRESH.UpdateWhisperCheckBox()
    TML.REFRESH.UpdateCollectorInputValues()

    if TML.REFRESH.status then
        TML.REFRESH.status()
    end
end

------------------------------------------------------------
-- TML.UI LOG TML.REFRESH
------------------------------------------------------------

TML.REFRESH.log = function()
    if not TML.UI.root or not TML.UI.root:IsVisible() then
        return
    end

    if not TML.UI.bopScroll or not TML.UI.boeScroll then
        return
    end

    TML.UI.bopScroll:Clear()
    -- boeScroll is same frame, already cleared

    local lootLog = RTMasterLooterSave.lootLog or {}
    local hasAny = false

    for index = 1, #lootLog do
        local entry = lootLog[index]

        if not TML.ITEM.IsIgnoredLootItem(entry.item) then
            local line = TML.LOG.FormatLogLine(entry)

            if TML.ITEM.IsItemBOE(entry.item) then
                -- Orange [BoE] prefix so BOE items stand out
                TML.UI.bopScroll:AddMessage("|cffFF8C00[BoE]|r " .. line)
            else
                TML.UI.bopScroll:AddMessage(line)
            end
            hasAny = true
        end
    end

    if not hasAny then
        TML.UI.bopScroll:AddMessage("No loot logs.")
    end

    if TML.UI.bopCounter then
        TML.UI.bopCounter:SetText("|cffFF8000Shard: " .. tostring((RTMasterLooterSave.stats and RTMasterLooterSave.stats.shards) or 0) .. "|r")
    end

    if TML.UI.boeCounter then
        TML.UI.boeCounter:SetText("|cff00ff00BOE: " .. tostring((RTMasterLooterSave.stats and RTMasterLooterSave.stats.boe) or 0) .. "|r")
    end

    TML.UI.bopScroll:ScrollToBottom()
end

function TML.REFRESH.RefreshUI()
    if TML.REFRESH.controls then
        TML.REFRESH.controls()
    end

    if TML.REFRESH.log then
        TML.REFRESH.log()
    end
end

------------------------------------------------------------
-- TML.UI BUILDERS
------------------------------------------------------------

function TML.BUILDER.CreateLogScrollFrame(parent, globalName)
    local scroll = CreateFrame("ScrollingMessageFrame", globalName, parent)
    scroll:SetPoint("TOPLEFT", 8, -26)
    scroll:SetPoint("BOTTOMRIGHT", -8, 8)
    scroll:SetFontObject(ChatFontSmall)
    scroll:SetJustifyH("LEFT")
    scroll:SetFading(false)
    scroll:SetMaxLines(TML.CONST.MAX_LOG_ENTRIES)
    scroll:SetInsertMode("BOTTOM")
    scroll:SetIndentedWordWrap(false)
    scroll:EnableMouse(true)
    scroll:EnableMouseWheel(true)

    scroll:SetScript("OnMouseWheel", function(self, delta)
        if delta > 0 then
            self:ScrollUp()
        else
            self:ScrollDown()
        end
    end)

    return scroll
end

function TML.BUILDER.CreateLogPanel(parent, skinModule, side)
    local panel = TML.BUILDER.CreateSectionPanel(parent, 1, TML.CONST.LOG_SECTION_HEIGHT)

    local title = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetTextColor(1, 0.82, 0, 1)

    local counter = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    counter:SetTextColor(1, 0.82, 0, 1)

    local clearButton = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    clearButton:SetSize(46, 18)
    clearButton:SetText("Clear")
    TML.STYLE.SkinButton(clearButton, skinModule)

    local scrollName
    if side == "LEFT" then
        scrollName = "TMLMasterLooterBOPScrollFrame"
    else
        scrollName = "TMLMasterLooterBOEScrollFrame"
    end

    local scroll = TML.BUILDER.CreateLogScrollFrame(panel, scrollName)

    if side == "LEFT" then
        title:SetText("Recent BOP Loot")
        counter:SetText("Shard: 0")
        counter:SetTextColor(1, 0.5, 0, 1)
    else
        title:SetText("Recent BOE Loot")
        counter:SetText("BOE: 0")
        counter:SetTextColor(0, 1, 0, 1)
    end

    return panel, title, counter, clearButton, scroll
end

function TML.BUILDER.CreateLogSection(parentFrame, skinModule)
    local section = CreateFrame("Frame", nil, parentFrame)
    section:SetPoint("TOPLEFT", TML.CONST.OUTER_LEFT, TML.CONST.TOP_OFFSET)
    section:SetPoint("TOPRIGHT", -TML.CONST.OUTER_RIGHT, TML.CONST.TOP_OFFSET)
    section:SetHeight(TML.CONST.LOG_SECTION_HEIGHT)

    TML.UI.logSection = section

    -- Log panel: left half only (matched to collector width via UpdateBottomSections)
    local panel = TML.BUILDER.CreateSectionPanel(section, 1, TML.CONST.LOG_SECTION_HEIGHT)
    panel:SetPoint("TOPLEFT", 0, 0)
    panel:SetWidth(400)  -- placeholder; UpdateBottomSections sets real width

    -- Title: "Recent BOP + BOE Loot"
    local title = section:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetTextColor(1, 0.82, 0, 1)
    title:SetText("Recent BOP + BOE Loot")
    -- [3px] Title [10px] Shard:0 [3px] ClrBOP [10px] BOE:0 [3px] ClrBOE
    -- All anchored from TOPLEFT of panel so nothing overlaps
    title:SetPoint("BOTTOMLEFT", panel, "TOPLEFT", 3, 4)

    -- Shard counter: right after title (~170px)
    local shardCounter = section:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    shardCounter:SetTextColor(1, 0.5, 0, 1)
    shardCounter:SetText("Shard: 0")
    shardCounter:SetPoint("BOTTOMLEFT", panel, "TOPLEFT", 173, 4)

    -- Clear BOP button: 3px after Shard label (~55px wide)
    local bopClearButton = CreateFrame("Button", nil, section, "UIPanelButtonTemplate")
    bopClearButton:SetSize(55, 18)
    bopClearButton:SetText("Clr BOP")
    TML.STYLE.SkinButton(bopClearButton, skinModule)
    bopClearButton:SetPoint("BOTTOMLEFT", panel, "TOPLEFT", 228, 2)

    -- BOE counter: 10px after ClrBOP button
    local boeCounter = section:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    boeCounter:SetTextColor(0, 1, 0, 1)
    boeCounter:SetText("BOE: 0")
    boeCounter:SetPoint("BOTTOMLEFT", panel, "TOPLEFT", 296, 4)

    -- Clear BOE button: 3px after BOE label
    local boeClearButton = CreateFrame("Button", nil, section, "UIPanelButtonTemplate")
    boeClearButton:SetSize(55, 18)
    boeClearButton:SetText("Clr BOE")
    TML.STYLE.SkinButton(boeClearButton, skinModule)
    boeClearButton:SetPoint("BOTTOMLEFT", panel, "TOPLEFT", 340, 2)

    -- Single scroll for both BOP and BOE
    local scroll = TML.BUILDER.CreateLogScrollFrame(panel, "TMLMasterLooterMergedScrollFrame")

    -- Store refs (bopPanel/boePanel point to same panel for compat)
    TML.UI.bopPanel        = panel
    TML.UI.boePanel        = panel
    TML.UI.bopTitle        = title
    TML.UI.boeTitle        = title
    TML.UI.bopCounter      = shardCounter
    TML.UI.boeCounter      = boeCounter
    TML.UI.bopClearButton  = bopClearButton
    TML.UI.boeClearButton  = boeClearButton
    -- Single scroll handles both
    TML.UI.bopScroll       = scroll
    TML.UI.boeScroll       = scroll

    bopClearButton:SetScript("OnClick", function() TML.LOG.ClearBOPHistory() end)
    boeClearButton:SetScript("OnClick", function() TML.LOG.ClearBOEHistory() end)

    section:SetScript("OnSizeChanged", function(self)
        local w = self:GetWidth()
        if w and w > 10 then panel:SetWidth(w) end
    end)
end

function TML.BUILDER.CreateStatusSection(parentFrame, skinModule)
    local panel = TML.BUILDER.CreateSectionPanel(parentFrame, 1, TML.CONST.STATUS_HEIGHT)

    local statusText = panel:CreateFontString(nil, "OVERLAY")
    statusText:SetFont(STANDARD_TEXT_FONT, 16, "OUTLINE")
    statusText:SetJustifyH("LEFT")
    statusText:SetPoint("TOPLEFT", panel, "TOPLEFT", 12, -TML.CONST.STATUS_INNER_TOP_PADDING)
    statusText:SetPoint("RIGHT", panel, "RIGHT", -12, 0)

    local raidLeaderText = panel:CreateFontString(nil, "OVERLAY")
    raidLeaderText:SetFont(STANDARD_TEXT_FONT, 14, "OUTLINE")
    raidLeaderText:SetJustifyH("LEFT")
    raidLeaderText:SetPoint("TOPLEFT", statusText, "BOTTOMLEFT", 0, -8)
    raidLeaderText:SetPoint("RIGHT", panel, "RIGHT", -12, 0)

    local makeMLButton = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    makeMLButton:SetSize(140, 24)
    makeMLButton:SetText("Make Me ML")
    makeMLButton:SetPoint("TOPLEFT", raidLeaderText, "BOTTOMLEFT", 0, -10)
    TML.STYLE.SkinButton(makeMLButton, skinModule)

    makeMLButton:SetScript("OnClick", function()
        SetLootMethod("master", TML.HELPER.GetPlayerName())
        if TML.REFRESH.status then
            TML.REFRESH.status()
        end
    end)

    -- Start Master Looter button - bottom-right of status panel
    local toggleButton = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    toggleButton:SetSize(170, 28)
    toggleButton:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -8, 8)
    TML.STYLE.SkinButton(toggleButton, skinModule)
    toggleButton:SetScript("OnClick", function()
        TML.STATE.isEnabled = not TML.STATE.isEnabled
        RTMasterLooterSave.enabled = TML.STATE.isEnabled
        if RefreshStatusOverlay then RefreshStatusOverlay() end
        if TML.REFRESH.controls then TML.REFRESH.controls() end
    end)

    TML.UI.statusPanel    = panel
    TML.UI.statusText     = statusText
    TML.UI.raidLeaderText = raidLeaderText
    TML.UI.makeMLButton   = makeMLButton
    TML.UI.toggleButton   = toggleButton
end

function TML.BUILDER.CreateCollectorRow(parentFrame, skinModule, labelText, saveKey, yOffset)
    local row = CreateFrame("Frame", nil, parentFrame)
    row:SetPoint("TOPLEFT", 8, yOffset)
    row:SetPoint("TOPRIGHT", -8, yOffset)
    row:SetHeight(24)

    local label = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    label:SetPoint("LEFT", 0, 0)
    label:SetWidth(TML.CONST.LABEL_WIDTH)
    label:SetJustifyH("LEFT")
    label:SetText(labelText)

    local selfButton = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
    selfButton:SetSize(TML.CONST.SELF_WIDTH, 24)
    selfButton:SetPoint("RIGHT", 0, 0)
    selfButton:SetText("Self")
    TML.STYLE.SkinButton(selfButton, skinModule)

    local targetButton = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
    targetButton:SetSize(TML.CONST.TARGET_WIDTH, 24)
    targetButton:SetPoint("RIGHT", selfButton, "LEFT", -4, 0)
    targetButton:SetText("Target")
    TML.STYLE.SkinButton(targetButton, skinModule)

    local inputBackground, editBox = TML.BUILDER.CreatePixelInput(row, TML.CONST.INPUT_WIDTH, 24)
    inputBackground:SetPoint("LEFT", label, "RIGHT", 8, 0)
    inputBackground:SetPoint("RIGHT", targetButton, "LEFT", -6, 0)

    editBox:SetText(RTMasterLooterSave[saveKey] or TML.HELPER.GetPlayerName())
    editBox:SetScript("OnTextChanged", function(self)
        RTMasterLooterSave[saveKey] = self:GetText()
    end)

    targetButton:SetScript("OnClick", function()
        TML.BUILDER.SetCollectorValue(editBox, saveKey, UnitName("target"))
    end)

    selfButton:SetScript("OnClick", function()
        TML.BUILDER.SetCollectorValue(editBox, saveKey, TML.HELPER.GetPlayerName())
    end)

    TML.UI.collectorInputs[saveKey] = editBox
end

function TML.BUILDER.CreateCollectorSection(parentFrame, skinModule)
    local panel = TML.BUILDER.CreateSectionPanel(parentFrame, 1, TML.CONST.COLLECTOR_HEIGHT)

    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("BOTTOMLEFT", panel, "TOPLEFT", 0, TML.CONST.COLLECTOR_TITLE_GAP)
    title:SetTextColor(1, 0.82, 0, 1)
    title:SetText("Collectors")

    TML.BUILDER.CreateCollectorRow(panel, skinModule, "Main Loot:", "lootCollector", -12)
    TML.BUILDER.CreateCollectorRow(panel, skinModule, "Shard:", "shardsCollector", -42)
    TML.BUILDER.CreateCollectorRow(panel, skinModule, "BoE Collector:", "boeCollector", -72)

    TML.UI.collectorPanel = panel
end

function TML.BUILDER.CreateWhisperSection(parentFrame, skinModule)
    local checkBox = CreateFrame("CheckButton", nil, parentFrame, "ChatConfigCheckButtonTemplate")
    checkBox:SetSize(24, 24)
    checkBox:SetPoint("BOTTOMLEFT", 16, 10)
    TML.STYLE.SkinCheckBox(checkBox, skinModule)

    checkBox:SetScript("OnClick", function(self)
        RTMasterLooterSave.whisperEnabled = self:GetChecked() and true or false
    end)

    local label = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    label:SetPoint("LEFT", checkBox, "RIGHT", 2, 0)
    label:SetText("Whisper Winner")

    TML.UI.whisperCheckBox = checkBox
    TML.UI.whisperLabel = label
end

function TML.BUILDER.CreateBottomButtons(parentFrame, onClose, skinModule)
    -- toggleButton placed inside status panel (center-right) after status section built
    -- Store onClose for later use
    TML.UI._onClose = onClose
    -- Close button removed - Start ML moved into status panel by CreateStatusSection
end

------------------------------------------------------------
-- RM UI refs (declared here so both CreateMasterLooterTabContent and
-- RM_Refresh* functions below share the same upvalue)
------------------------------------------------------------
local RM_panel       = nil
local RM_listParent  = nil  -- roll list scroll child
local RM_itemParent  = nil  -- item list scroll child
local RM_statusFS    = nil
local RM_countFS     = nil
local RM_selectedFS  = nil
local RM_noPermFS    = nil
local RM_rollBtns    = {}
local RM_skipSort    = false
local RM_cdBtnMain   = nil   -- AceGUI Button in main RM tab
local RM_cdBtnPanel  = nil   -- raw RMBtn in float panel

------------------------------------------------------------
-- PUBLIC TML.UI ENTRY
------------------------------------------------------------

function TML.API.CreateMasterLooterTabContent(parent, onClose)
    TML.DB.EnsureSavedVariables()
    TML.LOG.RebuildStatsFromLootLog()

    local COL_W = 491

    local f = CreateFrame("Frame", nil, parent)
    f:SetAllPoints(parent)
    MLSetBG(f, unpack(ML_C.bg))
    TML.UI.root = f
    TML.UI.collectorInputs = {}

    -- ── Left column scroll frame (Master Looter) — original width, centered ─
    local lScroll = CreateFrame("ScrollFrame", "RNToolsMLScrollFrame", f, "UIPanelScrollFrameTemplate")
    lScroll:SetPoint("TOP",    f, "TOP",    0, -10)
    lScroll:SetPoint("BOTTOM", f, "BOTTOM", 0,  36)
    lScroll:SetWidth(COL_W)
    lScroll:EnableMouseWheel(false)
    _G["RNToolsMLScrollFrameScrollBar"]:Hide()
    local lContent = CreateFrame("Frame", nil, lScroll)
    lContent:SetWidth(COL_W)
    lScroll:SetScrollChild(lContent)

    -- ── Right column (Roll Manager) — hidden but kept for mini RM state ─
    local ROLL_PANEL_H = 172
    local rBottom = CreateFrame("Frame", nil, f)
    rBottom:SetPoint("BOTTOMLEFT",  f, "BOTTOMLEFT",  503, 36)
    rBottom:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -16, 36)
    rBottom:SetHeight(ROLL_PANEL_H)
    rBottom:Hide()

    local rScroll = CreateFrame("ScrollFrame", "RNToolsRMScrollFrame", f, "UIPanelScrollFrameTemplate")
    rScroll:SetPoint("TOPLEFT",    f, "TOPLEFT",    503, -10)
    rScroll:SetPoint("BOTTOMLEFT", rBottom, "TOPLEFT", 0, 0)
    rScroll:SetWidth(COL_W)
    rScroll:EnableMouseWheel(false)
    _G["RNToolsRMScrollFrameScrollBar"]:Hide()
    rScroll:Hide()
    local rContent = CreateFrame("Frame", nil, rScroll)
    rContent:SetWidth(COL_W)
    rScroll:SetScrollChild(rContent)

    -- ── Layout helpers ─────────────────────────────────────────────────
    local lyOffset = 0
    local function PlaceL(widget, h, sp)
        widget.frame:SetParent(lContent)
        widget.frame:ClearAllPoints()
        widget.frame:SetPoint("TOPLEFT",  lContent, "TOPLEFT",  0, -lyOffset)
        widget.frame:SetPoint("TOPRIGHT", lContent, "TOPRIGHT", 0, -lyOffset)
        widget.frame:SetHeight(h)
        widget.frame:Show()
        lyOffset = lyOffset + h + (sp or 0)
    end
    local function PlaceLRaw(raw, h, sp)
        raw:SetParent(lContent)
        raw:ClearAllPoints()
        raw:SetPoint("TOPLEFT",  lContent, "TOPLEFT",  0, -lyOffset)
        raw:SetPoint("TOPRIGHT", lContent, "TOPRIGHT", 0, -lyOffset)
        raw:SetHeight(h)
        raw:Show()
        lyOffset = lyOffset + h + (sp or 0)
    end
    local function NewLRow(h, sp)
        local row = AceGUI:Create("SimpleGroup")
        row:SetLayout("Flow")
        row.noAutoHeight = true
        row:SetWidth(COL_W)
        PlaceL(row, h, sp)
        return row
    end

    local ryOffset = 0
    local function PlaceR(widget, h, sp)
        widget.frame:SetParent(rContent)
        widget.frame:ClearAllPoints()
        widget.frame:SetPoint("TOPLEFT",  rContent, "TOPLEFT",  0, -ryOffset)
        widget.frame:SetPoint("TOPRIGHT", rContent, "TOPRIGHT", 0, -ryOffset)
        widget.frame:SetHeight(h)
        widget.frame:Show()
        ryOffset = ryOffset + h + (sp or 0)
    end
    local function PlaceRRaw(raw, h, sp)
        raw:SetParent(rContent)
        raw:ClearAllPoints()
        raw:SetPoint("TOPLEFT",  rContent, "TOPLEFT",  0, -ryOffset)
        raw:SetPoint("TOPRIGHT", rContent, "TOPRIGHT", 0, -ryOffset)
        raw:SetHeight(h)
        raw:Show()
        ryOffset = ryOffset + h + (sp or 0)
    end
    local function NewRRow(h, sp)
        local row = AceGUI:Create("SimpleGroup")
        row:SetLayout("Flow")
        row.noAutoHeight = true
        row:SetWidth(COL_W)
        PlaceR(row, h, sp)
        return row
    end

    -- ═══════════════════ LEFT COLUMN: MASTER LOOTER ═══════════════════

    local mlHead = AceGUI:Create("Heading")
    mlHead:SetText("Master Looter")
    PlaceL(mlHead, 18, 4)

    -- Log controls row: title | shard | boe | Clr BOP | Clr BOE
    local logHdrRow = NewLRow(24, 4)
    local logTitleLbl = AceGUI:Create("Label")
    logTitleLbl:SetText("|cffFFD100Recent Loot|r")
    logTitleLbl:SetRelativeWidth(0.22)
    logHdrRow:AddChild(logTitleLbl)
    local bopCountLbl = AceGUI:Create("Label")
    bopCountLbl:SetText("|cffFF8000Shard: 0|r")
    bopCountLbl:SetRelativeWidth(0.17)
    logHdrRow:AddChild(bopCountLbl)
    TML.UI.bopCounter = bopCountLbl
    local boeCountLbl = AceGUI:Create("Label")
    boeCountLbl:SetText("|cff00ff00BOE: 0|r")
    boeCountLbl:SetRelativeWidth(0.14)
    logHdrRow:AddChild(boeCountLbl)
    TML.UI.boeCounter = boeCountLbl
    local clrBopBtn = AceGUI:Create("Button")
    clrBopBtn:SetText("Clr BOP")
    clrBopBtn:SetRelativeWidth(0.23)
    clrBopBtn:SetCallback("OnClick", function() TML.LOG.ClearBOPHistory() end)
    logHdrRow:AddChild(clrBopBtn)
    TML.UI.bopClearButton = clrBopBtn
    local clrBoeBtn = AceGUI:Create("Button")
    clrBoeBtn:SetText("Clr BOE")
    clrBoeBtn:SetRelativeWidth(0.24)
    clrBoeBtn:SetCallback("OnClick", function() TML.LOG.ClearBOEHistory() end)
    logHdrRow:AddChild(clrBoeBtn)
    TML.UI.boeClearButton = clrBoeBtn

    -- Log ScrollingMessageFrame inside a raw holder
    local logHolder = CreateFrame("Frame", nil, lContent)
    local bopScroll = CreateFrame("ScrollingMessageFrame", "TMLMasterLooterMergedScrollFrame", logHolder)
    bopScroll:SetPoint("TOPLEFT",     logHolder, "TOPLEFT",     0, 0)
    bopScroll:SetPoint("BOTTOMRIGHT", logHolder, "BOTTOMRIGHT", 0, 0)
    bopScroll:SetFontObject(ChatFontSmall)
    bopScroll:SetJustifyH("LEFT")
    bopScroll:SetFading(false)
    bopScroll:SetMaxLines(TML.CONST.MAX_LOG_ENTRIES)
    bopScroll:SetInsertMode("BOTTOM")
    bopScroll:SetIndentedWordWrap(false)
    bopScroll:EnableMouse(true)
    bopScroll:EnableMouseWheel(true)
    bopScroll:SetScript("OnMouseWheel", function(self, delta)
        if delta > 0 then self:ScrollUp() else self:ScrollDown() end
    end)
    PlaceLRaw(logHolder, 160, 8)
    TML.UI.bopScroll = bopScroll
    TML.UI.boeScroll = bopScroll

    -- Status
    local statusHead = AceGUI:Create("Heading")
    statusHead:SetText("Status")
    PlaceL(statusHead, 18, 4)

    local statusHolder = CreateFrame("Frame", nil, lContent)
    local statusText = statusHolder:CreateFontString(nil, "OVERLAY")
    statusText:SetFont(STANDARD_TEXT_FONT, 16, "OUTLINE")
    statusText:SetJustifyH("LEFT")
    statusText:SetPoint("TOPLEFT",  statusHolder, "TOPLEFT",  2, -2)
    statusText:SetPoint("TOPRIGHT", statusHolder, "TOPRIGHT", -2, -2)
    local raidLeaderText = statusHolder:CreateFontString(nil, "OVERLAY")
    raidLeaderText:SetFont(STANDARD_TEXT_FONT, 14, "OUTLINE")
    raidLeaderText:SetJustifyH("LEFT")
    raidLeaderText:SetPoint("TOPLEFT",  statusHolder, "TOPLEFT",  2, -24)
    raidLeaderText:SetPoint("TOPRIGHT", statusHolder, "TOPRIGHT", -2, -24)
    PlaceLRaw(statusHolder, 44, 4)
    TML.UI.statusText     = statusText
    TML.UI.raidLeaderText = raidLeaderText

    local makeMLBtn = AceGUI:Create("Button")
    makeMLBtn:SetText("Make Me ML")
    makeMLBtn:SetCallback("OnClick", function()
        SetLootMethod("master", TML.HELPER.GetPlayerName())
        if TML.REFRESH.status then TML.REFRESH.status() end
    end)
    PlaceL(makeMLBtn, 24, 8)
    TML.UI.makeMLButton = makeMLBtn

    -- Collectors
    local colHead = AceGUI:Create("Heading")
    colHead:SetText("Collectors")
    PlaceL(colHead, 18, 4)

    local function MLGetRosterNames()
        local names, seen = {}, {}
        for i = 1, GetNumRaidMembers() do
            local name = GetRaidRosterInfo(i)
            if name then
                name = name:match("^([^-]+)") or name
                if not seen[name] then seen[name]=true; names[#names+1]=name end
            end
        end
        for i = 1, GetNumPartyMembers() do
            local name = UnitName("party"..i)
            if name and not seen[name] then seen[name]=true; names[#names+1]=name end
        end
        local me = UnitName("player")
        if me and not seen[me] then names[#names+1]=me end
        return names
    end

    local function MLCreateAutocomplete(ebWidget, saveKey)
        local SUGG_H = 18
        local popup = CreateFrame("Frame", nil, f)
        popup:SetFrameStrata("TOOLTIP")
        popup:SetBackdrop({
            bgFile="Interface\\Buttons\\WHITE8X8",
            edgeFile="Interface\\Buttons\\WHITE8X8",
            edgeSize=1, insets={left=1,right=1,top=1,bottom=1},
        })
        popup:SetBackdropColor(0.08, 0.08, 0.08, 0.97)
        popup:SetBackdropBorderColor(0.55, 0.50, 0.42, 1)
        popup:Hide()
        popup._btns = {}

        local function ShowSugg(text)
            for _, btn in ipairs(popup._btns) do btn:Hide() end
            local matches = {}
            if text and text ~= "" then
                local lower = text:lower()
                for _, name in ipairs(MLGetRosterNames()) do
                    if name:lower():sub(1, #lower) == lower then
                        matches[#matches+1] = name
                        if #matches >= 6 then break end
                    end
                end
            end
            if #matches == 0 then popup:Hide(); return end
            for i, name in ipairs(matches) do
                local btn = popup._btns[i]
                if not btn then
                    btn = CreateFrame("Button", nil, popup)
                    btn:SetHeight(SUGG_H)
                    local fs = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                    fs:SetPoint("LEFT", 4, 0); fs:SetPoint("RIGHT", -4, 0)
                    fs:SetJustifyH("LEFT")
                    btn._fs = fs
                    btn:SetScript("OnEnter", function(self) self:SetAlpha(0.75) end)
                    btn:SetScript("OnLeave", function(self) self:SetAlpha(1) end)
                    btn:SetScript("OnClick", function(self)
                        ebWidget:SetText(self._fs:GetText())
                        popup:Hide()
                    end)
                    popup._btns[i] = btn
                end
                btn._fs:SetText(name)
                btn:ClearAllPoints()
                btn:SetPoint("TOPLEFT",  popup, "TOPLEFT",  2, -2-(i-1)*SUGG_H)
                btn:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -2, -2-(i-1)*SUGG_H)
                btn:Show()
            end
            popup:SetHeight(4 + #matches * SUGG_H)
            popup:ClearAllPoints()
            popup:SetPoint("TOPLEFT",  ebWidget.frame, "BOTTOMLEFT",  0, -1)
            popup:SetPoint("TOPRIGHT", ebWidget.frame, "BOTTOMRIGHT", 0, -1)
            popup:Show()
        end

        ebWidget:SetCallback("OnTextChanged", function(widget, event, text)
            RTMasterLooterSave[saveKey] = text
            ShowSugg(text)
        end)
        ebWidget.editbox:HookScript("OnEditFocusLost", function()
            C_Timer_fake(0.15, function() popup:Hide() end)
        end)
    end

    local collectorDefs = {
        { label = "Main Loot:",     saveKey = "lootCollector"   },
        { label = "Shard:",         saveKey = "shardsCollector" },
        { label = "BoE Collector:", saveKey = "boeCollector"    },
    }
    for _, def in ipairs(collectorDefs) do
        local colRow = NewLRow(26, 4)
        local colLbl = AceGUI:Create("Label")
        colLbl:SetText(def.label)
        colLbl:SetRelativeWidth(0.22)
        colRow:AddChild(colLbl)
        local colEB = AceGUI:Create("EditBox")
        colEB:DisableButton(true)
        colEB:SetRelativeWidth(0.34)
        colEB:SetText(RTMasterLooterSave[def.saveKey] or TML.HELPER.GetPlayerName())
        colRow:AddChild(colEB)
        local targetBtn = AceGUI:Create("Button")
        targetBtn:SetText("Target")
        targetBtn:SetRelativeWidth(0.22)
        targetBtn:SetCallback("OnClick", function()
            TML.BUILDER.SetCollectorValue(colEB, def.saveKey, UnitName("target"))
        end)
        colRow:AddChild(targetBtn)
        local selfBtn = AceGUI:Create("Button")
        selfBtn:SetText("Self")
        selfBtn:SetRelativeWidth(0.22)
        selfBtn:SetCallback("OnClick", function()
            TML.BUILDER.SetCollectorValue(colEB, def.saveKey, TML.HELPER.GetPlayerName())
        end)
        colRow:AddChild(selfBtn)
        TML.UI.collectorInputs[def.saveKey] = colEB
        MLCreateAutocomplete(colEB, def.saveKey)
    end

    -- Whisper checkbox
    local whisperCB = AceGUI:Create("CheckBox")
    whisperCB:SetLabel("Whisper Winner")
    whisperCB:SetValue(RTMasterLooterSave.whisperEnabled == true)
    whisperCB:SetCallback("OnValueChanged", function(widget, event, value)
        RTMasterLooterSave.whisperEnabled = value
    end)
    PlaceL(whisperCB, 24, 6)
    TML.UI.whisperCheckBox = whisperCB

    -- Start/Stop Master Looter button
    local toggleBtn = AceGUI:Create("Button")
    PlaceL(toggleBtn, 28, 0)
    TML.UI.toggleButton = toggleBtn
    toggleBtn:SetCallback("OnClick", function()
        TML.STATE.isEnabled = not TML.STATE.isEnabled
        RTMasterLooterSave.enabled = TML.STATE.isEnabled
        if RefreshStatusOverlay then RefreshStatusOverlay() end
        if TML.REFRESH.controls then TML.REFRESH.controls() end
    end)

    lContent:SetHeight(lyOffset)

    -- ═══════════════════ RIGHT COLUMN: ROLL MANAGER ═══════════════════

    local rmHead = AceGUI:Create("Heading")
    rmHead:SetText("Loot  (click to select item for roll)")
    PlaceR(rmHead, 18, 4)

    -- Selected item display (raw FontString in a holder frame)
    local selHolder = CreateFrame("Frame", nil, rContent)
    RM_selectedFS = selHolder:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    RM_selectedFS:SetPoint("TOPLEFT",  selHolder, "TOPLEFT",  2, -2)
    RM_selectedFS:SetPoint("TOPRIGHT", selHolder, "TOPRIGHT", -2, -2)
    RM_selectedFS:SetNonSpaceWrap(false)
    RM_selectedFS:SetText("|cff666666No item selected|r")
    PlaceRRaw(selHolder, 16, 4)


    -- Item list scroll frame (raw pool, kept for performance)
    local ITEM_SF_H = 160
    local itemSF = CreateFrame("ScrollFrame", "RNRollItemSF", rContent, "UIPanelScrollFrameTemplate")
    itemSF:SetScript("OnSizeChanged", function(s, w)
        if w and w > 10 then RM_itemParent:SetWidth(w) end
    end)
    itemSF:SetScript("OnScrollRangeChanged", function(self, xr, yr)
        local bar = _G["RNRollItemSFScrollBar"]
        if not bar then return end
        yr = math.floor(yr + 0.5)
        bar:SetMinMaxValues(0, yr); bar:SetValue(math.min(bar:GetValue(), yr))
        local up   = _G["RNRollItemSFScrollBarScrollUpButton"]
        local down = _G["RNRollItemSFScrollBarScrollDownButton"]
        local v = bar:GetValue()
        if up   then if v > 0  then up:Enable()   else up:Disable()   end end
        if down then if v < yr then down:Enable() else down:Disable() end end
    end)
    local itemSC = CreateFrame("Frame", nil, itemSF)
    itemSC:SetWidth(COL_W)
    itemSC:SetHeight(800)
    itemSF:SetScrollChild(itemSC)
    RM_itemParent = itemSC
    PlaceRRaw(itemSF, ITEM_SF_H, 16)

    RM_noPermFS = rContent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    RM_noPermFS:SetPoint("CENTER", itemSF, "CENTER", 0, 0)
    RM_noPermFS:SetTextColor(0.5, 0.5, 0.5, 1)
    RM_noPermFS:SetText("|cffFF8C00Roll Manager: Raid Leader / Master Looter / Assist only|r")
    RM_noPermFS:Hide()

    -- Start Roll heading
    local rollHead = AceGUI:Create("Heading")
    rollHead:SetText("Start Roll")
    PlaceR(rollHead, 18, 4)

    -- Roll type + stop + cd buttons in one row
    local rollBtnRow = NewRRow(26, 4)
    local msBtn = AceGUI:Create("Button")
    msBtn:SetText("|cff20ff60MS|r")
    msBtn:SetRelativeWidth(0.16)
    msBtn:SetCallback("OnClick", function() RM_AnnounceRoll("MS") end)
    rollBtnRow:AddChild(msBtn)
    local osBtn = AceGUI:Create("Button")
    osBtn:SetText("|cffFFD100OS|r")
    osBtn:SetRelativeWidth(0.16)
    osBtn:SetCallback("OnClick", function() RM_AnnounceRoll("OS") end)
    rollBtnRow:AddChild(osBtn)
    local freeBtn = AceGUI:Create("Button")
    freeBtn:SetText("|cffaaaaFFFree|r")
    freeBtn:SetRelativeWidth(0.16)
    freeBtn:SetCallback("OnClick", function() RM_AnnounceRoll("Free") end)
    rollBtnRow:AddChild(freeBtn)
    local stopBtn = AceGUI:Create("Button")
    stopBtn:SetText("|cffff4444Stop Roll|r")
    stopBtn:SetRelativeWidth(0.22)
    stopBtn:SetCallback("OnClick", function() RM_StopRoll() end)
    rollBtnRow:AddChild(stopBtn)

    -- CD duration (EasyMenu via raw frame script on the AceGUI button's frame)
    local RM_CDDDMenu = CreateFrame("Frame", "RNRollCDDDMenu", UIParent, "UIDropDownMenuTemplate")
    local cdOpenBtn = nil
    local cdBtn = AceGUI:Create("Button")
    cdBtn:SetText("CD: " .. RM_cdDuration .. "s")
    cdBtn:SetRelativeWidth(0.30)
    cdBtn.frame:SetScript("OnClick", function(self)
        if cdOpenBtn == self then
            CloseDropDownMenus(); cdOpenBtn = nil; return
        end
        cdOpenBtn = self
        local menu = {}
        for _, secs in ipairs({0, 10, 15, 20, 25, 30}) do
            local capSecs = secs
            menu[#menu + 1] = {
                text    = capSecs == 0 and "(none)" or capSecs .. " sec",
                checked = (RM_cdDuration == capSecs),
                func    = function()
                    RM_cdDuration = capSecs
                    cdOpenBtn = nil
                    if type(RTRollManagerSave) ~= "table" then RTRollManagerSave = {} end
                    RTRollManagerSave.cdDuration = capSecs
                    RNTools_SyncCDLabel()
                    local msg = capSecs == 0 and "Countdown disabled — close rolls manually." or "Countdown set to " .. capSecs .. " seconds."
                    DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r " .. msg)
                end,
            }
        end
        EasyMenu(menu, RM_CDDDMenu, self, 0, 0, "MENU")
    end)
    RM_cdBtnMain = cdBtn
    rollBtnRow:AddChild(cdBtn)

    if not RM_HasPermission() then
        msBtn:SetDisabled(true); osBtn:SetDisabled(true); freeBtn:SetDisabled(true)
    end

    -- Status / count / permission display
    local statusHolder2 = CreateFrame("Frame", nil, rContent)
    RM_statusFS = statusHolder2:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    RM_statusFS:SetPoint("TOPLEFT",  statusHolder2, "TOPLEFT",  2, -2)
    RM_statusFS:SetPoint("TOPRIGHT", statusHolder2, "TOPRIGHT", -2, -2)
    RM_statusFS:SetText("|cff666666Idle — select an item and start a roll|r")
    RM_countFS = statusHolder2:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    RM_countFS:SetPoint("TOPLEFT",  statusHolder2, "TOPLEFT",  2, -18)
    RM_countFS:SetPoint("TOPRIGHT", statusHolder2, "TOPRIGHT", -2, -18)
    RM_countFS:SetTextColor(0.60, 0.60, 0.60, 1)
    RM_countFS:SetText("0 rolls")
    local permFS = statusHolder2:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    permFS:SetPoint("TOPRIGHT", statusHolder2, "TOPRIGHT", -2, -2)
    if RM_HasPermission() then
        permFS:SetText("|cff20ff60You are ML/RL|r")
    else
        permFS:SetText("|cffff4444You are not RL/ML|r")
    end
    PlaceRRaw(statusHolder2, 36, 14)

    -- Permission + button enable/disable ticker (every 2s)
    local permTick = 0
    local awardBtn  -- forward ref, assigned below
    rContent:SetScript("OnUpdate", function(self, elapsed)
        permTick = permTick + elapsed
        if permTick < 2 then return end
        permTick = 0
        if RM_HasPermission() then
            permFS:SetText("|cff20ff60You are ML/RL|r")
            msBtn:SetDisabled(false); osBtn:SetDisabled(false)
            freeBtn:SetDisabled(false); stopBtn:SetDisabled(false)
            if awardBtn then awardBtn:Enable() end
        else
            permFS:SetText("|cffff4444You are not RL/ML|r")
            msBtn:SetDisabled(true); osBtn:SetDisabled(true)
            freeBtn:SetDisabled(true); stopBtn:SetDisabled(true)
            if awardBtn then awardBtn:Disable() end
        end
    end)

    -- Rolls section — fixed in rBottom (does not scroll)
    local rbOffset = 0
    local function PlaceRB(widget, h, sp)
        widget.frame:SetParent(rBottom)
        widget.frame:ClearAllPoints()
        widget.frame:SetPoint("TOPLEFT",  rBottom, "TOPLEFT",  0, -rbOffset)
        widget.frame:SetPoint("TOPRIGHT", rBottom, "TOPRIGHT", 0, -rbOffset)
        widget.frame:Show()
        rbOffset = rbOffset + h + (sp or 0)
    end
    local function PlaceRBRaw(raw, h, sp)
        raw:SetParent(rBottom)
        raw:ClearAllPoints()
        raw:SetPoint("TOPLEFT",  rBottom, "TOPLEFT",  0, -rbOffset)
        raw:SetPoint("TOPRIGHT", rBottom, "TOPRIGHT", 0, -rbOffset)
        rbOffset = rbOffset + h + (sp or 0)
    end
    local function NewRBRow(h, sp)
        local row = AceGUI:Create("SimpleGroup")
        row:SetLayout("Flow")
        row.noAutoHeight = true
        row:SetWidth(COL_W)
        PlaceRB(row, h, sp)
        return row
    end

    local rollsHead = AceGUI:Create("Heading")
    rollsHead:SetText("Rolls  (click name to select winner)")
    PlaceRB(rollsHead, 18, 4)

    -- Roll list — scrollable, clipped to its container
    local ROLL_SF_H = 114
    local rollSF = CreateFrame("ScrollFrame", "RNToolsRMRollSF", rBottom, "UIPanelScrollFrameTemplate")
    rollSF:SetScript("OnSizeChanged", function(s, w)
        if w and w > 10 then RM_listParent:SetWidth(w) end
    end)
    rollSF:SetScript("OnScrollRangeChanged", function(self, xr, yr)
        local bar = _G["RNToolsRMRollSFScrollBar"]
        if not bar then return end
        yr = math.floor(yr + 0.5)
        bar:SetMinMaxValues(0, yr); bar:SetValue(math.min(bar:GetValue(), yr))
        local up   = _G["RNToolsRMRollSFScrollBarScrollUpButton"]
        local down = _G["RNToolsRMRollSFScrollBarScrollDownButton"]
        if up   then if bar:GetValue() > 0  then up:Enable()   else up:Disable()   end end
        if down then if bar:GetValue() < yr then down:Enable() else down:Disable() end end
    end)
    local rollSC = CreateFrame("Frame", nil, rollSF)
    rollSC:SetWidth(COL_W)
    rollSC:SetHeight(600)
    rollSF:SetScrollChild(rollSC)
    RM_listParent = rollSC
    PlaceRBRaw(rollSF, ROLL_SF_H, 6)

    -- Bottom action buttons — raw buttons directly on rBottom, no container
    local function MakeRMBtn(label, onClick)
        local b = CreateFrame("Button", nil, rBottom, "UIPanelButtonTemplate")
        b:SetHeight(24)
        b:SetText(label)
        b:SetScript("OnClick", onClick)
        return b
    end

    local BTN_Y = -(rbOffset + 2)
    local bClearRolls = MakeRMBtn("|cffff4d4dClear Rolls|r", function() RM_ClearRolls() end)
    bClearRolls:SetPoint("LEFT", rBottom, "LEFT", 0, BTN_Y)
    bClearRolls:SetWidth(110)

    awardBtn = MakeRMBtn("|cff20ff60Award Winner|r", function()
        if not RM_winner then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Roll Manager]:|r No winner selected.")
            return
        end
        if not RM_selectedIdx or not RM_items[RM_selectedIdx] then return end
        local roll = 0
        for _, entry in ipairs(RM_rolls) do if entry.name == RM_winner then roll = entry.roll; break end end
        RM_ShowAwardPopup(RM_selectedIdx, RM_winner, roll)
    end)
    awardBtn:SetPoint("LEFT", bClearRolls, "RIGHT", 4, 0)
    awardBtn:SetWidth(120)

    local bClearItems = MakeRMBtn("|cffFFD100Clear Items|r", function()
        RM_ClearItems(); RM_RefreshItemList()
    end)
    bClearItems:SetPoint("LEFT", awardBtn, "RIGHT", 4, 0)
    bClearItems:SetWidth(100)

    local bHistory = MakeRMBtn("History", function()
        if RNTools_ToggleHistory then RNTools_ToggleHistory() end
    end)
    bHistory:SetPoint("LEFT", bClearItems, "RIGHT", 4, 0)
    bHistory:SetWidth(90)

    rContent:SetHeight(ryOffset)

    -- ── OnShow / status ticker ─────────────────────────────────────────
    f:SetScript("OnShow", function()
        TML.DB.EnsureSavedVariables()
        TML.LOG.RebuildStatsFromLootLog()
        TML.STATE.isEnabled = RTMasterLooterSave.enabled == true
        TML.REFRESH.RefreshUI()
        RM_RefreshRollList()
        RM_RefreshItemList()
    end)

    f:SetScript("OnUpdate", function(self, elapsed)
        TML.STATE.statusElapsed = TML.STATE.statusElapsed + elapsed
        if TML.STATE.statusElapsed < 0.2 then return end
        TML.STATE.statusElapsed = 0
        if TML.REFRESH.status then TML.REFRESH.status() end
    end)

    return f
end



function CreateMasterLooterTabContent(parent, onClose)
    return TML.API.CreateMasterLooterTabContent(parent, onClose)
end

------------------------------------------------------------
-- EVENT HANDLERS
------------------------------------------------------------

function TML.EVENTS.HandleAddonLoaded(loadedAddonName)
    if loadedAddonName ~= addonName then
        return
    end

    TML.DB.EnsureSavedVariables()
    TML.LOG.RebuildStatsFromLootLog()
    TML.STATE.isEnabled = RTMasterLooterSave.enabled == true

    if RefreshStatusOverlay then
        RefreshStatusOverlay()
    end

    TML.LOADER_FRAME:UnregisterEvent("ADDON_LOADED")
    -- Restore item list from last session (deferred - function defined later in file)
    local RM_loadDefer = CreateFrame("Frame")
    RM_loadDefer:SetScript("OnUpdate", function(self)
        self:SetScript("OnUpdate", nil)
        RM_LoadItemList()
        -- Restore CD duration from saved settings
        if type(RTRollManagerSave) ~= "table" then RTRollManagerSave = {} end
        if RTRollManagerSave.cdDuration and RTRollManagerSave.cdDuration >= 0 then
            RM_cdDuration = RTRollManagerSave.cdDuration
        end
    end)
end

function TML.EVENTS.HandleMainEvent(event, ...)
    if event == "LOOT_OPENED" then
        TML.LOGIC.DistributeLoot()
        return
    end

    if event == "CHAT_MSG_LOOT" then
        TML.LOGIC.HandleLootMessage(...)
        return
    end

    if event == "PARTY_LOOT_METHOD_CHANGED" then
        if TML.REFRESH.status then
            TML.REFRESH.status()
        end
        return
    end

    if event == "GROUP_ROSTER_UPDATE" then
        if TML.REFRESH.status then
            TML.REFRESH.status()
        end
        return
    end

    if event == "PLAYER_ENTERING_WORLD" then
        if TML.REFRESH.status then
            TML.REFRESH.status()
        end
    end
end

------------------------------------------------------------
-- EVENT REGISTRATION
------------------------------------------------------------

TML.LOADER_FRAME:RegisterEvent("ADDON_LOADED")
TML.LOADER_FRAME:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        TML.EVENTS.HandleAddonLoaded(...)
    end
end)

TML.EVENT_FRAME:RegisterEvent("LOOT_OPENED")
TML.EVENT_FRAME:RegisterEvent("CHAT_MSG_LOOT")
TML.EVENT_FRAME:RegisterEvent("PARTY_LOOT_METHOD_CHANGED")
TML.EVENT_FRAME:RegisterEvent("GROUP_ROSTER_UPDATE")
TML.EVENT_FRAME:RegisterEvent("PLAYER_ENTERING_WORLD")
TML.EVENT_FRAME:SetScript("OnEvent", function(self, event, ...)
    TML.EVENTS.HandleMainEvent(event, ...)
end)

-- Global toggle for Quick Access window
function RNTools_ToggleMasterLooter()
    TML.STATE.isEnabled = not TML.STATE.isEnabled
    RTMasterLooterSave.enabled = TML.STATE.isEnabled
    TML.REFRESH.UpdateToggleButtonText()
    if RefreshStatusOverlay then RefreshStatusOverlay() end
end


-------------------------------------------------------------------------------
-- RNTools Roll Manager
-- Tracks loot drops, select item to roll on, announces MS/OS/Free with item link,
-- records /roll results, sorts by highest, select winner, award.
-------------------------------------------------------------------------------

local RM = {}

-- Item list (all drops from current loot window)
RM_items       = {}   -- {link, name, texture, quality} (global for Mini RM)
RM_itemCount   = 0
RM_selectedIdx = nil
local RM_itemBtns    = {}   -- UI buttons for item list

-- Trade timer: hidden tooltip for scanning bind time
local RM_timerTooltip = nil
local RM_timerTicker  = 0

-- Parse time string into total minutes
-- Handles: "1 hour 58 min", "45 min", "2 hours 5 min", "4 min", "1 hour"
local function RM_ParseMinutes(s)
    if not s then return nil end
    local hours   = s:match("(%d+)%s*hour")
    local minutes = s:match("(%d+)%s*min")
    local total   = ((tonumber(hours) or 0) * 60) + (tonumber(minutes) or 0)
    return total > 0 and total or nil
end

-- Format minutes into coloured display string
local function RM_FormatTradeTime(mins)
    if not mins then return nil end
    local color
    if mins <= 5 then
        color = "|cffff4444"   -- red: under 5 min
    elseif mins <= 30 then
        color = "|cffFFD100"   -- yellow: under 30 min
    else
        color = "|cff20ff60"   -- green: 30 min+
    end
    local h = math.floor(mins / 60)
    local m = mins % 60
    local timeStr
    if h > 0 and m > 0 then
        timeStr = h .. "h " .. m .. "m"
    elseif h > 0 then
        timeStr = h .. "h"
    else
        timeStr = m .. "m"
    end
    return color .. timeStr .. "|r"
end

local function RM_GetTradeTimeRemaining(bagID, slotID)
    if not RM_timerTooltip then
        RM_timerTooltip = CreateFrame("GameTooltip", "RNRollTimerTip", UIParent, "GameTooltipTemplate")
        RM_timerTooltip:SetOwner(UIParent, "ANCHOR_NONE")
    end
    RM_timerTooltip:ClearLines()
    RM_timerTooltip:SetBagItem(bagID, slotID)
    -- Scan all lines for the trade eligibility sentence
    -- 3.3.5a text: "You may trade this item with players that were also eligible
    --               to loot this item for the next X hour(s) Y min."
    for line = 1, RM_timerTooltip:NumLines() do
        local t = _G["RNRollTimerTipTextLeft"..line]
        if t then
            local s = t:GetText()
            if s then
                -- Match "for the next X hour Y min" or "for the next X min"
                local timeChunk = s:match("for the next (.+)$")
                if timeChunk then
                    local mins = RM_ParseMinutes(timeChunk)
                    if mins then
                        return RM_FormatTradeTime(mins), mins
                    end
                end
            end
        end
    end
    return nil, nil
end

-- Scan all bags to find a matching item link, return bag+slot
local function RM_FindItemInBags(itemLink)
    if not itemLink then return nil, nil end
    for bag = 0, 4 do
        for slot = 1, GetContainerNumSlots(bag) do
            local link = GetContainerItemLink(bag, slot)
            if link and link == itemLink then
                return bag, slot
            end
        end
    end
    return nil, nil
end

-- Update trade timers for all items in the list
local function RM_UpdateTimers()
    local changed = false
    for i = 1, RM_itemCount do
        local item = RM_items[i]
        if not item.awarded then
            local bag, slot = RM_FindItemInBags(item.link)
            if bag then
                local timeStr, mins = RM_GetTradeTimeRemaining(bag, slot)
                if timeStr ~= item.tradeTime then
                    item.tradeTime     = timeStr
                    item.tradeTimeMins = mins
                    changed = true
                end
            elseif item.tradeTime ~= nil then
                item.tradeTime = nil
                changed = true
            end
        end
    end
    -- Always refresh so time display updates even without data change
    if RM_RefreshItemList then RM_RefreshItemList() end
end

-- Timer update frame: scans every 30s, only if has permission
local RM_timerFrame = CreateFrame("Frame")
local RM_displayTicker = 0  -- separate fast ticker just for display refresh
RM_timerFrame:SetScript("OnUpdate", function(self, elapsed)
    -- Fast display refresh every 30s (just updates text, no tooltip scan)
    RM_displayTicker = RM_displayTicker + elapsed
    if RM_displayTicker >= 30 then
        RM_displayTicker = 0
        if RM_itemCount > 0 and RM_RefreshItemList then
            RM_RefreshItemList()
        end
    end
    -- Full timer scan (tooltip scrape) every 60s
    RM_timerTicker = RM_timerTicker + elapsed
    if RM_timerTicker < 60 then return end
    RM_timerTicker = 0
    if RM_itemCount > 0 then
        RM_UpdateTimers()
    end
end)

-- Roll data
RM_rolls     = {}
RM_rollCount = 0

-- Roll DB: stores all rolls per item link for history display
-- RNToolsRollDB[itemLink] = { {name, roll, rollType, time}, ... }

local function RM_InitRollDB()
    if type(RNToolsRollDB) ~= "table" then RNToolsRollDB = {} end
end

local function RM_SaveRollsToDB(itemLink, rollType)
    RM_InitRollDB()
    if not itemLink or RM_rollCount == 0 then return end
    if not RNToolsRollDB[itemLink] then RNToolsRollDB[itemLink] = {} end
    local entry = {
        rollType = rollType or RM_rollLabel,
        time     = date("%m/%d/%Y %H:%M"),
        rolls    = {},
    }
    for i = 1, RM_rollCount do
        table.insert(entry.rolls, {
            name       = RM_rolls[i].name,
            roll       = RM_rolls[i].roll,
            ineligible = RM_rolls[i].ineligible or nil,
            ineligSpec = RM_rolls[i].ineligSpec or nil,
        })
    end
    -- Insert at front (newest first)
    table.insert(RNToolsRollDB[itemLink], 1, entry)
end

function RM_GetRollHistory(itemLink)
    RM_InitRollDB()
    return RNToolsRollDB and RNToolsRollDB[itemLink] or nil
end

-- Persist item list across sessions
function RM_SaveItemList()
    RNToolsItemList = {}
    for i = 1, RM_itemCount do
        local item = RM_items[i]
        if item then
            RNToolsItemList[i] = {
                link    = item.link,
                name    = item.name,
                texture = item.texture,
                quality = item.quality,
                awarded = item.awarded or false,
                winner  = item.winner  or nil,
                lootIdx = item.lootIdx or i,
            }
        end
    end
end

function RM_LoadItemList()
    if type(RNToolsItemList) ~= "table" then return end
    RM_items     = {}
    RM_itemCount = 0
    for i = 1, #RNToolsItemList do
        local saved = RNToolsItemList[i]
        if saved and saved.link then
            RM_itemCount = RM_itemCount + 1
            RM_items[RM_itemCount] = {
                link    = saved.link,
                name    = saved.name    or "",
                texture = saved.texture or "Interface\\Icons\\INV_Misc_QuestionMark",
                quality = saved.quality or 4,
                awarded = saved.awarded or false,
                winner  = saved.winner  or nil,
                lootIdx = saved.lootIdx or RM_itemCount,
            }
        end
    end
    if RM_itemCount > 0 then
        DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r Restored " .. RM_itemCount .. " item(s).")
        RM_RefreshItemList()
        -- Set a flag so mini RM refreshes its list the moment it's first opened
        RM_needsMiniRefresh = true
    end
end

-- Save on logout AND on zone change (catches more exit scenarios)
local RM_logoutFrame = CreateFrame("Frame")
RM_logoutFrame:RegisterEvent("PLAYER_LOGOUT")
RM_logoutFrame:RegisterEvent("PLAYER_LEAVING_WORLD")
RM_logoutFrame:SetScript("OnEvent", function(self, event)
    RM_SaveItemList()
    if type(RTRollManagerSave) ~= "table" then RTRollManagerSave = {} end
    RTRollManagerSave.cdDuration = RM_cdDuration
    if type(RNToolsLootHistory) ~= "table" then RNToolsLootHistory = {} end
end)

-- Loot History SavedVariable init
local function RM_InitHistory()
    if type(RNToolsLootHistory) ~= "table" then RNToolsLootHistory = {} end
end

-- Record a roll result to loot history
-- Find position of a player in the roll list (1 = highest roll)
local function RM_GetWinnerPosition(name)
    for i = 1, RM_rollCount do
        if RM_rolls[i] and RM_rolls[i].name == name then
            return i
        end
    end
    return 1  -- fallback
end

function RM_RecordHistory(itemLink, rollType, winnerName, winnerRoll, position)
    RM_InitHistory()
    -- Date key: M/D/YYYY
    local dateStr = date("%m/%d/%Y")
    local timeStr = date("%H:%M")
    if not RNToolsLootHistory[dateStr] then
        RNToolsLootHistory[dateStr] = {}
        RNToolsLootHistory[dateStr]._order = dateStr  -- for sorting
    end
    local entry
    if winnerName and winnerName ~= "No Winner" then
        entry = itemLink .. " (" .. (rollType or "MS") .. ") Winner -> " ..
                winnerName .. " rolled " .. (winnerRoll or 0) ..
                " (#" .. (position or 1) .. ") (" .. timeStr .. ")"
    else
        entry = itemLink .. " (" .. (rollType or "MS") .. ") Winner -> " ..
                "Item Skipped: No rolls recorded (" .. timeStr .. ")"
    end
    -- Insert at position 1 so newest is on top within a date
    table.insert(RNToolsLootHistory[dateStr], 1, entry)
end
RM_winner        = nil
RM_recording     = false
RM_canRoll       = true
RM_rerolled      = {}
local RM_warned  = false
RM_rollLabel     = "Roll"
RM_rollAnnouncer = nil  -- name of player who opened the current roll

-- True only for the player who actually sent the /rw to open this roll
function RM_IsAnnouncer()
    return RM_rollAnnouncer ~= nil and RM_rollAnnouncer == UnitName("player")
end

-- Countdown
RM_cdRunning      = false
RM_stopTriggered  = false
local RM_cdStart    = 0
local RM_cdPos      = 0
RM_cdDuration = 20
function RNTools_SyncCDLabel()
    local d = RM_cdDuration or 20
    local txt = d == 0 and "CD: --" or ("CD: " .. d .. "s")
    if RM_cdBtnMain  then RM_cdBtnMain:SetText(txt) end
    if RM_cdBtnPanel then RM_cdBtnPanel._fs:SetText(txt) end
    if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
end

-- UI refs declared near CreateMasterLooterTabContent above

-- Quality colours (all qualities including grey/white for testing)
local RM_QUAL_COLORS = {
    [0]="#9d9d9d",[1]="#ffffff",[2]="#1eff00",[3]="#0070dd",[4]="#a335ee",[5]="#ff8000",
}

-- Class colours
local RM_CLASS_COLORS = {
    DEATHKNIGHT="ffc41f3b",DRUID="ffff7d0a",HUNTER="ffabd473",MAGE="ff69ccf0",
    PALADIN="fff58cba",PRIEST="ffffffff",ROGUE="fffff569",SHAMAN="ff0070de",
    WARLOCK="ff9482c9",WARRIOR="ffc79c6e",
}

local RM_BD = {bgFile="Interface\\Buttons\\WHITE8X8",
    edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize=12,insets={left=3,right=3,top=3,bottom=3}}

local function RMBtn(parent, label, w, h)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(w, h)
    btn:SetBackdrop(RM_BD)
    btn:SetBackdropColor(0.16,0.14,0.11,1)
    btn:SetBackdropBorderColor(0.55,0.50,0.42,1.0)
    local hl = btn:GetHighlightTexture()
    if not hl then hl = btn:CreateTexture(nil,"HIGHLIGHT") end
    hl:SetTexture("Interface\\Buttons\\WHITE8X8")
    hl:SetVertexColor(0.55,0.50,0.42,0.20); hl:SetAllPoints(btn)
    local fs = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    fs:SetAllPoints(btn); fs:SetJustifyH("CENTER"); fs:SetText(label)
    fs:SetTextColor(0.90,0.90,0.90,1)
    btn._fs = fs
    btn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.28,0.24,0.18,1)
        self:SetBackdropBorderColor(0.85,0.75,0.55,1)
    end)
    btn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(0.16,0.14,0.11,1)
        self:SetBackdropBorderColor(0.55,0.50,0.42,1.0)
    end)
    btn:SetScript("OnMouseDown", function(self) self:SetBackdropColor(0.08,0.08,0.08,1) end)
    btn:SetScript("OnMouseUp",   function(self) self:SetBackdropColor(0.28,0.24,0.18,1) end)
    return btn
end

local function RMHdr(parent, text, x, y, w)
    local f = CreateFrame("Frame", nil, parent)
    f:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    f:SetSize(w, 20)
    f:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        edgeSize=1,insets={left=1,right=1,top=1,bottom=1}})
    f:SetBackdropColor(0.16,0.14,0.11,1)
    f:SetBackdropBorderColor(0.55,0.50,0.42,0.8)
    local fs = f:CreateFontString(nil,"OVERLAY","GameFontNormal")
    fs:SetPoint("CENTER",f,"CENTER",0,0)
    fs:SetTextColor(1,0.82,0,1); fs:SetText(text)
    return f
end

-- ── Item tracking ─────────────────────────────────────────────────────

function RM_ClearItems()
    RM_items       = {}
    RM_itemCount   = 0
    RM_selectedIdx = nil
    if RM_selectedFS then RM_selectedFS:SetText("|cff666666No item selected|r") end
    -- Hide all item buttons
    for i = 1, #RM_itemBtns do
        if RM_itemBtns[i] then RM_itemBtns[i]:Hide() end
    end
end

-- Mark item as awarded (strikethrough colour) but keep in list
function RM_MarkAwarded(idx, winnerName)
    if not idx or not RM_items[idx] then return end
    RM_items[idx].awarded = true
    RM_items[idx].winner  = winnerName or "?"
end

-- Remove a single item from the list
local function RM_RemoveItem(idx)
    if not idx or not RM_items[idx] then return end
    table.remove(RM_items, idx)
    RM_itemCount = RM_itemCount - 1
    if RM_selectedIdx == idx then
        RM_selectedIdx = nil
        if RM_selectedFS then RM_selectedFS:SetText("|cff666666No item selected|r") end
    elseif RM_selectedIdx and RM_selectedIdx > idx then
        RM_selectedIdx = RM_selectedIdx - 1
    end
    RM_RefreshItemList()
end

local function RM_AddItem(link, quality)
    if not link then return end
    local name, _, q, _, _, _, _, _, _, tex = GetItemInfo(link)
    if not name then return end
    q = quality or q or 0
    RM_itemCount = RM_itemCount + 1
    RM_items[RM_itemCount] = {link=link, name=name, texture=tex, quality=q}
    RM_SaveItemList()
end

-- RM_noPermFS declared near CreateMasterLooterTabContent above

-- Sort state
-- RM_sortBy / RM_skipSort declared near CreateMasterLooterTabContent above

local function RM_SortItems()
    if RM_skipSort then return end
    -- Fixed order: rolling item on top, awarded at bottom, newest first in between
    table.sort(RM_items, function(a, b)
        local aRoll = (a == RM_rollingItem)
        local bRoll = (b == RM_rollingItem)
        if aRoll and not bRoll then return true end
        if bRoll and not aRoll then return false end
        if a.awarded and not b.awarded then return false end
        if b.awarded and not a.awarded then return true end
        return (a.lootIdx or 0) < (b.lootIdx or 0)
    end)
end

-- RB-style backdrop (same as RaidBrowser rows - proven stable)
local RM_BACKDROP = {
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    tile=false, tileSize=1, edgeSize=1,
    insets={left=1,right=1,top=1,bottom=1},
}
local function RM_SetBG(f,r,g,b,a)
    if not f._rmbd then f:SetBackdrop(RM_BACKDROP); f._rmbd=true end
    f:SetBackdropColor(r,g,b,a or 1)
end
local function RM_SetBorder(f,r,g,b,a)
    if not f._rmbd then f:SetBackdrop(RM_BACKDROP); f._rmbd=true end
    f:SetBackdropBorderColor(r,g,b,a or 1)
end

-- RM_noPermFS declared near CreateMasterLooterTabContent above

-- Global refresh trigger - can be called from outside even before tab opened
function RM_ForceRefresh()
    if RM_itemParent then
        RM_RefreshItemList()
    end
    -- If RM_itemParent is nil, tab hasn't been opened yet
    -- Data is updated correctly in RM_items, visual will populate when tab opens
end

function RM_RefreshItemList()
    if not RM_itemParent then return end
    RM_SortItems()
    -- Hide all pooled buttons
    for i = 1, #RM_itemBtns do RM_itemBtns[i]:Hide() end
    if RM_noPermFS then
        if RM_itemCount == 0 then RM_noPermFS:Show() else RM_noPermFS:Hide() end
    end

    local ROW_H = 24
    local GAP   = 2

    for i = 1, RM_itemCount do
        local item = RM_items[i]
        if not item then break end
        local btn = RM_itemBtns[i]

        if not btn then
            -- Create row using RB-style SetBackdrop (no separate textures)
            btn = CreateFrame("Button", nil, RM_itemParent)
            btn:SetHeight(ROW_H)
            RM_SetBG(btn, 0.04, 0.07, 0.12, 0.95)
            RM_SetBorder(btn, 0.30,0.28,0.24,1)

            -- Icon [LEFT+3]
            local icon = btn:CreateTexture(nil,"ARTWORK")
            icon:SetSize(20,20)
            icon:SetPoint("LEFT", btn, "LEFT", 3, 0)
            btn._icon = icon

            -- Item name [LEFT+27] to mid-point
            local nameFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
            nameFS:SetPoint("LEFT",  btn, "LEFT",  27, 0)
            nameFS:SetPoint("RIGHT", btn, "RIGHT", -90, 0)
            nameFS:SetJustifyH("LEFT")
            btn._nameFS = nameFS

            -- Time remaining [RIGHT-30] right-aligned before x button
            local timeFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
            timeFS:SetPoint("RIGHT", btn, "RIGHT", -24, 0)
            timeFS:SetJustifyH("RIGHT")
            btn._timeFS = timeFS

            -- [x] delete button [RIGHT-2, width 20]
            local xBtn = CreateFrame("Button", nil, btn)
            xBtn:SetSize(20, 18)
            xBtn:SetPoint("RIGHT", btn, "RIGHT", -2, 0)
            RM_SetBG(xBtn, 0.22, 0.04, 0.04, 1)
            RM_SetBorder(xBtn, 0.80, 0.20, 0.10, 1)
            local xFS = xBtn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
            xFS:SetAllPoints(xBtn); xFS:SetJustifyH("CENTER")
            xFS:SetText("x"); xFS:SetTextColor(1,0.4,0.4,1)
            btn._xBtn = xBtn

            -- Row OnClick: select item via _itemRef
            -- Do NOT call RM_RefreshItemList here - it re-sorts and items jump around
            -- Just update selection state and visuals directly
            btn:SetScript("OnClick", function(self)
                local ref = self._itemRef
                if not ref then return end
                -- Block ANY selection change while roll is in progress
                if RM_recording then
                    DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r Roll in progress - stop the roll first.")
                    return
                end
                for idx = 1, RM_itemCount do
                    if RM_items[idx] == ref then
                        RM_selectedIdx = idx
                        if RM_selectedFS then
                            RM_selectedFS:SetText("Item: "..ref.link)
                        end
                        -- If Done item: load its roll history into the roll list
                        if ref.awarded and ref.link then
                            -- Always clear first so previous roll list doesn't bleed through
                            RM_rolls     = {}
                            RM_rollCount = 0
                            RM_winner    = nil
                            local hist = RM_GetRollHistory(ref.link)
                            if hist and #hist > 0 then
                                local last = hist[1]
                                for _, r in ipairs(last.rolls or {}) do
                                    RM_rollCount = RM_rollCount + 1
                                    RM_rolls[RM_rollCount] = {
                                        name       = r.name,
                                        roll       = r.roll,
                                        ineligible = r.ineligible or nil,
                                        ineligSpec = r.ineligSpec or nil,
                                    }
                                end
                                if RM_rollCount > 0 then
                                    RM_winner = ref.winner or RM_rolls[1].name
                                end
                            end
                            RM_RefreshRollList()
                        end
                        -- Update ALL row colours - selected wins, then awarded, then normal
                        for j = 1, #RM_itemBtns do
                            local b = RM_itemBtns[j]
                            if b and b._itemRef and b:IsShown() then
                                if b._itemRef == ref then
                                    -- This item: selected (green)
                                    RM_SetBG(b, 0.04,0.20,0.08,1)
                                    RM_SetBorder(b, 0.10,0.80,0.30,1)
                                elseif b._itemRef.awarded then
                                    -- Done item: dark grey
                                    RM_SetBG(b, 0.03,0.03,0.03,0.7)
                                    RM_SetBorder(b, 0.15,0.15,0.15,0.5)
                                else
                                    -- Normal item: alternating rows
                                    RM_SetBG(b, j%2==0 and 0.10 or 0.13,
                                               j%2==0 and 0.10 or 0.13,
                                               j%2==0 and 0.10 or 0.13, 0.95)
                                    RM_SetBorder(b, 0.30,0.28,0.24,1)
                                end
                            end
                        end
                        return
                    end
                end
            end)

            -- xBtn OnClick: find by ref, remove, refresh immediately
            xBtn:SetScript("OnClick", function()
                local ref = btn._itemRef
                if not ref then return end
                for idx = 1, RM_itemCount do
                    if RM_items[idx] == ref then
                        table.remove(RM_items, idx)
                        RM_itemCount = RM_itemCount - 1
                        if RM_selectedIdx == idx then
                            RM_selectedIdx = nil
                            if RM_selectedFS then
                                RM_selectedFS:SetText("|cff666666No item selected|r")
                            end
                        elseif RM_selectedIdx and RM_selectedIdx > idx then
                            RM_selectedIdx = RM_selectedIdx - 1
                        end
                        RM_RefreshItemList()
                        return
                    end
                end
            end)

            -- Hover: tooltip (only when actually inside the scroll frame viewport)
            btn:SetScript("OnEnter", function(self)
                local sf = _G["RNRollItemSF"]
                if sf then
                    local bBot = self:GetBottom(); local sfBot = sf:GetBottom()
                    local bTop = self:GetTop();   local sfTop = sf:GetTop()
                    if not bBot or not sfBot or bBot < sfBot - 4 or bTop > sfTop + 4 then return end
                end
                RM_SetBG(self, 0.28,0.24,0.18,1)
                local ref = self._itemRef
                if ref and ref.link then
                    GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                    GameTooltip:SetHyperlink(ref.link)
                    GameTooltip:Show()
                end
            end)
            btn:SetScript("OnLeave", function(self)
                GameTooltip:Hide()
                local ref = self._itemRef
                local isSel = false
                for idx = 1, RM_itemCount do
                    if RM_items[idx] == ref and RM_selectedIdx == idx then
                        isSel = true; break
                    end
                end
                if isSel then
                    RM_SetBG(self, 0.04, 0.20, 0.08, 1)
                    RM_SetBorder(self, 0.10, 0.80, 0.30, 1)
                elseif ref and ref.awarded then
                    RM_SetBG(self, 0.03, 0.03, 0.03, 0.7)
                    RM_SetBorder(self, 0.15, 0.15, 0.15, 0.5)
                else
                    RM_SetBG(self, i%2==0 and 0.10 or 0.13,
                                  i%2==0 and 0.10 or 0.13,
                                  i%2==0 and 0.10 or 0.13, 0.95)
                    RM_SetBorder(self, 0.30,0.28,0.24,1)
                end
            end)

            RM_itemBtns[i] = btn
        end

        -- Always update position + ref every refresh
        btn:ClearAllPoints()
        btn:SetPoint("TOPLEFT",  RM_itemParent, "TOPLEFT",  0, -(i-1)*(ROW_H+GAP))
        btn:SetPoint("TOPRIGHT", RM_itemParent, "TOPRIGHT", 0, -(i-1)*(ROW_H+GAP))
        btn._itemRef = item  -- set BEFORE any script could read it

        -- Row colours
        local isSel = (RM_selectedIdx == i)
        if item.awarded then
            RM_SetBG(btn, 0.03, 0.03, 0.03, 0.7)
            RM_SetBorder(btn, 0.15, 0.15, 0.15, 0.5)
        elseif isSel then
            RM_SetBG(btn, 0.04, 0.20, 0.08, 1)
            RM_SetBorder(btn, 0.10, 0.80, 0.30, 1)
        else
            RM_SetBG(btn, i%2==0 and 0.10 or 0.13,
                         i%2==0 and 0.10 or 0.13,
                         i%2==0 and 0.10 or 0.13, 0.95)
            RM_SetBorder(btn, 0.30,0.28,0.24,1)
        end

        -- Icon
        btn._icon:SetTexture(item.texture)

        -- Name
        local qc = (RM_QUAL_COLORS[item.quality] or "#ffffff"):gsub("#","ff")
        if item.awarded then
            local winText  = item.winner or "?"
            local winColor = winText=="Timeout" and "ff4444" or winText=="No Winner" and "888888" or "20ff60"
            btn._nameFS:SetText("|cff555555[Done] "..item.name.." - |cff"..winColor..winText.."|r")
            btn._timeFS:SetText("")
        elseif isSel and RM_recording then
            -- Show Rolling... indicator on active roll item
            btn._nameFS:SetText("|c"..qc..item.name.."|r |cffFFD100- Rolling...|r")
            if item.tradeTime then
                btn._timeFS:SetText(item.tradeTime)
            else
                btn._timeFS:SetText("")
            end
        else
            btn._nameFS:SetText("|c"..qc..item.name.."|r")
            if item.tradeTime then
                btn._timeFS:SetText(item.tradeTime)
            else
                btn._timeFS:SetText("|cff444444--|r")
            end
        end

        btn:Show()
    end
end


-- ── Roll engine ───────────────────────────────────────────────────────

local function RM_Sort()
    table.sort(RM_rolls, function(a,b) return a.roll > b.roll end)
    -- Winner = highest eligible roll; ineligible (wrong spec) rolls are tracked but skipped
    RM_winner = nil
    for i = 1, RM_rollCount do
        if not RM_rolls[i].ineligible then
            RM_winner = RM_rolls[i].name
            break
        end
    end
end

local function RM_DidRoll(name)
    for i = 1, RM_rollCount do
        if RM_rolls[i].name == name then return true end
    end
    return false
end

local function RM_AddRoll(name, roll)
    if RM_DidRoll(name) then
        local already = false
        for _, n in ipairs(RM_rerolled) do if n==name then already=true; break end end
        if not already then
            if RM_HasPermission() then
                SendChatMessage("You already rolled! Only 1 roll allowed.", "WHISPER", nil, name)
            end
            table.insert(RM_rerolled, name)
        end
        return
    end
    -- Spec eligibility: only RL/ML should announce denials, and only when Track Spec is enabled
    if RM_HasPermission() and RM_rollLabel == "MS" and RNTools_MiniTrackSpec ~= false and
       RNTools_SpecEnforce ~= false and
       RNTools_CheckRollEligibility and RM_selectedIdx and RM_items and RM_items[RM_selectedIdx] then
        local itemName = RM_items[RM_selectedIdx].name
        local allowed, reason = RNTools_CheckRollEligibility(name, itemName)
        if not allowed then
            local spec = (RNTools_GetPlayerSpec and RNTools_GetPlayerSpec(name)) or reason
            local ch = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
            SendChatMessage("[" .. spec .. "] cannot roll for this item", ch)
            DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r " .. name .. " [" .. spec .. "] ineligible")
            -- Still track the roll but mark ineligible — shown in red, not counted as winner
            RM_rollCount = RM_rollCount + 1
            RM_rolls[RM_rollCount] = {name=name, roll=tonumber(roll) or 0, ineligible=true, ineligSpec=spec}
            RM_Sort()
            RM_RefreshRollList()
            return
        end
    end
    RM_rollCount = RM_rollCount + 1
    RM_rolls[RM_rollCount] = {name=name, roll=tonumber(roll) or 0}
    RM_Sort()
    RM_RefreshRollList()
end

function RM_RefreshRollList()
    if not RM_listParent then return end
    for i = 1, #RM_rollBtns do RM_rollBtns[i]:Hide() end
    if RM_countFS then
        RM_countFS:SetText(RM_rollCount.." roll"..(RM_rollCount==1 and "" or "s"))
    end
    for i = 1, RM_rollCount do
        local entry = RM_rolls[i]
        local btn   = RM_rollBtns[i]
        if not btn then
            btn = CreateFrame("Button", nil, RM_listParent)
            btn:SetHeight(24)
            btn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",
                edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1,
                insets={left=1,right=1,top=1,bottom=1}})
            local nameFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
            nameFS:SetPoint("LEFT",btn,"LEFT",6,0); btn._nameFS = nameFS
            local rollFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
            rollFS:SetPoint("RIGHT",btn,"RIGHT",-6,0); btn._rollFS = rollFS
            local starFS = btn:CreateFontString(nil,"OVERLAY","GameFontNormal")
            starFS:SetPoint("LEFT",nameFS,"RIGHT",4,0)
            starFS:SetTextColor(1,0.82,0,1); btn._starFS = starFS
            local captI = i
            btn:SetScript("OnClick", function()
                if RM_rolls[captI] then RM_winner = RM_rolls[captI].name end
                RM_RefreshRollList()
            end)
            btn:SetScript("OnEnter", function() btn:SetBackdropColor(0.28,0.24,0.18,1) end)
            btn:SetScript("OnLeave", function()
                local w = RM_rolls[captI] and RM_rolls[captI].name == RM_winner
                btn:SetBackdropColor(w and 0.04 or (i%2==0 and 0.10 or 0.13),
                    w and 0.20 or (i%2==0 and 0.10 or 0.13),
                    w and 0.08 or (i%2==0 and 0.10 or 0.13), 0.95)
            end)
            RM_rollBtns[i] = btn
        end
        btn:ClearAllPoints()
        btn:SetPoint("TOPLEFT",  RM_listParent,"TOPLEFT",  0, -(i-1)*25)
        btn:SetPoint("TOPRIGHT", RM_listParent,"TOPRIGHT", 0, -(i-1)*25)
        local isW = (entry.name == RM_winner)
        if isW then
            btn:SetBackdropColor(0.04,0.20,0.08,1)
            btn:SetBackdropBorderColor(0.10,0.80,0.30,1)
        else
            local bg = i%2==0 and 0.10 or 0.13
            btn:SetBackdropColor(bg,bg,bg,0.95)
            btn:SetBackdropBorderColor(0.30,0.28,0.24,1)
        end
        local _,cls = UnitClass(entry.name)
        local cc = cls and RM_CLASS_COLORS[cls] or "ffffffff"
        if entry.ineligible then
            btn._nameFS:SetText("|c"..cc..entry.name.."|r |cffff4444["..(entry.ineligSpec or "?").."]|r")
            btn._rollFS:SetText("|cff888888"..entry.roll.."|r")
            btn._starFS:SetText("|cffff4444x|r")
        else
            btn._nameFS:SetText("|c"..cc..entry.name.."|r")
            btn._rollFS:SetText("|cffFFD100"..entry.roll.."|r")
            btn._starFS:SetText(isW and "[W]" or "")
        end
        btn:Show()
    end
    -- Update status
    if RM_statusFS then
        if RM_winner and RM_rollCount > 0 then
            RM_statusFS:SetText("Winner: |cff20ff60"..RM_winner.."|r ("..
                (RM_rolls[1] and RM_rolls[1].roll or 0)..")")
        elseif RM_recording then
            RM_statusFS:SetText("|cffFFD100["..RM_rollLabel.."] Open - /roll 1-100|r")
        else
            RM_statusFS:SetText("|cff666666Idle — select an item and start a roll|r")
        end
    end
end

function RM_ClearRolls()
    RM_rolls     = {}
    RM_rollCount = 0
    RM_rerolled  = {}
    RM_winner    = nil
    RM_recording = false
    RM_canRoll   = true
    RM_warned    = false
    RM_cdRunning = false
    RM_RefreshRollList()
end

local function RM_GetChannel()
    local p = RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile
    if p and p.rm_channel then return p.rm_channel end
    if GetNumRaidMembers() > 0 then return "RAID_WARNING" end
    if GetNumPartyMembers() > 0 then return "PARTY" end
    return "SAY"
end

-- After award: clear selection and auto-select first non-awarded item
local function RM_ClearAndAutoSelect()
    RM_selectedIdx = nil
    -- Find first non-awarded item to auto-select
    for i = 1, RM_itemCount do
        if RM_items[i] and not RM_items[i].awarded then
            RM_selectedIdx = i
            if RM_selectedFS then
                RM_selectedFS:SetText("Item: " .. RM_items[i].link)
            end
            return
        end
    end
    -- No non-awarded items left
    if RM_selectedFS then
        RM_selectedFS:SetText("|cff666666No item selected|r")
    end
end

function RM_AnnounceRoll(label)
    if not RM_HasPermission() then
        DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Roll Manager]:|r You need Raid Leader / Master Looter / Assist to announce rolls.")
        return
    end
    if not RM_selectedIdx or not RM_items[RM_selectedIdx] then
        DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Roll Manager]:|r Select an item first!")
        return
    end
    -- Un-award item if it was previously awarded/skipped (re-roll scenario)
    if RM_items[RM_selectedIdx].awarded then
        RM_items[RM_selectedIdx].awarded = false
        RM_items[RM_selectedIdx].winner  = nil
    end
    RM_ClearRolls()
    RM_stopTriggered = false
    RM_recording    = true
    RM_canRoll      = true
    RM_rollLabel    = label
    RM_rollingItem   = RM_items[RM_selectedIdx]  -- lock this item reference
    RM_rollAnnouncer = UnitName("player")        -- only we announce for this roll
    local link   = RM_items[RM_selectedIdx].link
    local ch     = RM_GetChannel()
    local p      = RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile
    local openTmpl = (p and p.rm_open_tmpl) or "{item} >> {label} - Drop your rolls! /roll 1-100"
    local openMsg  = openTmpl:gsub("{item}", link):gsub("{label}", label)
    SendChatMessage(openMsg, ch)
    -- Auto-start countdown (skipped when duration is 0 = manual close)
    if RM_cdDuration and RM_cdDuration > 0 then
        RM_cdRunning = true
        RM_cdStart   = GetTime()
        RM_cdPos     = RM_cdDuration + 1
    end
    RM_RefreshRollList()
    -- Refresh item lists so Rolling... indicator appears immediately
    RM_RefreshItemList()
    if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
end

-------------------------------------------------------------------------------
-- Award popup — uses WoW native StaticPopupDialogs
-- button1 = Give to Winner, button2 = Keep in Bags, button3 = Re-roll
-------------------------------------------------------------------------------
local RM_pendingAward = {}

StaticPopupDialogs["RNTOOLS_AWARD_WINNER"] = {
    text         = "Award %s\nWinner: |cff20ff60%s|r",
    button1      = "Give to Winner",
    button2      = "Keep in Bags",
    button3      = "Re-roll",
    OnAccept = function()
        local d = RM_pendingAward
        if d.canGive then
            local slot = d.slot
            local cMap = TML and TML.HELPER and TML.HELPER.BuildMasterLootCandidateMap and TML.HELPER.BuildMasterLootCandidateMap()
            local cIdx = cMap and cMap[d.winner]
            if cIdx then
                GiveMasterLoot(slot, cIdx)
                RM_lootSlotMap[d.itemLink] = nil
            else
                DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Roll Manager]:|r " .. (d.winner or "?") .. " is not a valid loot candidate.")
            end
        end
        RM_Award_Finalize(d.itemIdx, d.winner, d.roll)
    end,
    OnCancel = function()
        local d = RM_pendingAward
        RM_Award_Finalize(d.itemIdx, d.winner, d.roll)
    end,
    OnAlt = function()
        RM_winner      = nil
        RM_ClearRolls()
        RM_recording   = false
        RM_canRoll     = false
        RM_rollingItem = nil
        RM_RefreshRollList()
        RM_RefreshItemList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r Re-roll — select a roll type and start again.")
    end,
    timeout       = 0,
    whileDead     = true,
    hideOnEscape  = true,
    preferredIndex = 3,
}

local function RM_ShowAwardPopup(itemIdx, winnerName, rollNum)
    local item = RM_items[itemIdx]
    if not item then return end
    local roll    = rollNum or 0
    local slot    = RM_lootSlotMap[item.link]
    local canGive = RM_lootWindowOpen and slot ~= nil
        and TML and TML.HELPER
        and TML.HELPER.IsPlayerMasterLooter and TML.HELPER.IsPlayerMasterLooter()
    RM_pendingAward = {
        itemIdx  = itemIdx,
        winner   = winnerName,
        roll     = roll,
        slot     = slot,
        canGive  = canGive,
        itemLink = item.link,
    }
    local label = (item.name or "Item") .. (canGive and "" or " |cffff6666(loot window closed)|r")
    local winLabel = (winnerName or "?") .. "  (roll: " .. roll .. ")"
    StaticPopup_Show("RNTOOLS_AWARD_WINNER", label, winLabel)
end

-- Finalize an award: announce, mark awarded, save history, clear rolls
function RM_Award_Finalize(itemIdx, winner, roll)
    local item = RM_items[itemIdx]
    if not item then return end
    local link     = item.link
    local winnerPos = RM_GetWinnerPosition(winner)
    local myName   = UnitName("player")
    if RM_IsAnnouncer() then
        local winCh = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
        local p2 = RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile
        local winTmpl = (p2 and p2.rm_winner_tmpl) or "{item} >> Winner: {name} ({roll} - {label})"
        local rwMsg = winTmpl:gsub("{item}", link):gsub("{name}", winner):gsub("{roll}", tostring(roll)):gsub("{label}", RM_rollLabel)
        SendChatMessage(rwMsg, winCh)
        if winner ~= myName then
            SendChatMessage("Congratulations! You won " .. link ..
                " (" .. RM_rollLabel .. " - " .. roll ..
                " #" .. winnerPos ..
                "). Trade " .. myName .. " to receive your item.", "WHISPER", nil, winner)
        end
        RM_rollAnnouncer = nil
    end
    DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r Awarded " .. link .. " to |cff20ff60" .. winner .. "|r")
    RM_SaveRollsToDB(link, RM_rollLabel)
    RM_MarkAwarded(itemIdx, winner)
    RM_RecordHistory(link, RM_rollLabel, winner, roll, winnerPos)
    RM_ClearRolls()
    RM_ClearAndAutoSelect()
    RM_RefreshItemList()
    if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
end

function RM_Award()
    if not RM_winner then
        DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Roll Manager]:|r No winner yet.")
        return
    end
    local savedIdx = RM_selectedIdx
    if not savedIdx or not RM_items[savedIdx] then return end
    local roll = 0
    for _, entry in ipairs(RM_rolls) do
        if entry.name == RM_winner then roll = entry.roll; break end
    end
    RM_ShowAwardPopup(savedIdx, RM_winner, roll)
end

-- ── Event listeners ───────────────────────────────────────────────────

-- Parse rolls from system chat
local RM_sysFrame = CreateFrame("Frame")
RM_sysFrame:RegisterEvent("CHAT_MSG_SYSTEM")
RM_sysFrame:SetScript("OnEvent", function(self, event)
    local msg = arg1
    if not RM_recording or not msg then return end
    local name, roll, mn, mx = msg:match("^(.+) rolls (%d+) %((%d+)-(%d+)%)")
    if name and roll and mn == "1" and mx == "100" then
        if not RM_canRoll then
            if not RM_warned and RM_IsAnnouncer() then
                local ch = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
                SendChatMessage("Rolls are CLOSED!", ch)
                RM_warned = true
            end
            return
        end
        RM_AddRoll(name, tonumber(roll))
    end
end)

-- Helper: check if local player can use Roll Manager
-- (Raid Leader, Master Looter, or Assist/Officer)
function RM_HasPermission()
    if UnitIsPartyLeader("player") then return true end
    if UnitIsRaidOfficer("player") then return true end
    if TML and TML.HELPER and TML.HELPER.IsPlayerMasterLooter and TML.HELPER.IsPlayerMasterLooter() then return true end
    return false
end

-- Accumulate loot across ALL loot windows (never auto-clear)
-- Testing mode: tracks ALL qualities including grey/white
-- Production: change the "true or" to switch to epics-only (q >= 4)
-- Helper: add one item link to the RM list (deduplicates, handles uncached items)
function RM_TryAddItem(itemLink, fallbackQuality, bypassFilter)
    if not itemLink or itemLink:find("money:") then return false end
    -- Duplicate check
    for di = 1, RM_itemCount do
        if RM_items[di] and RM_items[di].link == itemLink then return false end
    end
    -- GetItemInfo may return nil for uncached items; fall back to link-embedded name
    local name, fullLink, qual, _, _, _, _, _, _, tex = GetItemInfo(itemLink)
    if not name then
        name = itemLink:match("%[(.-)%]") or "Unknown"
    end
    local q = qual or fallbackQuality or 1
    if q < 0 then return false end  -- skip truly invalid quality
    -- Quality filter from settings (skipped when ML module is on — ML needs all items)
    if not bypassFilter then
        local p = RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile
        if p then
            if q == 2 and not p.rm_qual_uncommon  then return false end
            if q == 3 and not p.rm_qual_rare      then return false end
            if q == 4 and not p.rm_qual_epic      then return false end
            if q == 5 and not p.rm_qual_legendary then return false end
            if q <= 1 then return false end  -- always skip grey/white
        end
    else
        if q <= 1 then return false end  -- always skip grey/white even for ML
    end
    RM_itemCount = RM_itemCount + 1
    RM_items[RM_itemCount] = {
        link    = fullLink or itemLink,
        name    = name,
        texture = tex or "Interface\\Icons\\INV_Misc_QuestionMark",
        quality = q,
        lootIdx = RM_itemCount,
    }
    return true
end

-- Loot slot tracking: maps itemLink → loot slot index while loot window is open
RM_lootSlotMap    = {}   -- [itemLink] = slotIndex
RM_lootWindowOpen = false

local RM_lootFrame = CreateFrame("Frame")
RM_lootFrame:RegisterEvent("LOOT_OPENED")
RM_lootFrame:RegisterEvent("LOOT_CLOSED")
RM_lootFrame:SetScript("OnEvent", function(self, event)
    if event == "LOOT_CLOSED" then
        RM_lootWindowOpen = false
        RM_lootSlotMap    = {}
        if RM_autoLootBtn then RM_autoLootBtn:Hide() end
        return
    end
    if event ~= "LOOT_OPENED" then return end

    local mlOn = TML_IsMasterLooterEnabled and TML_IsMasterLooterEnabled()

    -- ML on: existing path (only RL/assist adds items, ML distributes)
    -- ML off: add ALL quality-filtered items to RM so player can choose what to roll
    if mlOn and not RM_HasPermission() then return end

    local slotCount = GetNumLootItems()
    if not slotCount or slotCount == 0 then return end
    RM_lootWindowOpen = true
    RM_lootSlotMap    = {}
    -- Bypass quality filter when player is the actual WoW master looter
    -- (covers both: RNTools ML module ON, and plain WoW ML without module)
    local isWoWML = TML and TML.HELPER and TML.HELPER.IsPlayerMasterLooter
        and TML.HELPER.IsPlayerMasterLooter()
    local bypassFilter = mlOn or isWoWML
    local added = false
    for slot = 1, slotCount do
        local _, _, _, quality = GetLootSlotInfo(slot)
        local itemLink = GetLootSlotLink(slot)
        if itemLink then
            RM_lootSlotMap[itemLink] = slot
            if RM_TryAddItem(itemLink, quality, bypassFilter) then added = true end
        end
    end
    if added then
        RM_SaveItemList()
        RM_skipSort = true
        RM_RefreshItemList()
        RM_skipSort = false
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if BuildMiniRollManager and (RM_MiniAutoShow ~= false) then BuildMiniRollManager() end
    end

    -- Show Auto-Loot button whenever loot window opens (ML on or off)
    C_Timer_fake(0.05, function() RM_ShowAutoLootButton() end)
end)

-------------------------------------------------------------------------------
-- RaidRoll winner detection
-- Listens for RaidRoll's winner announcements in raid/rw and feeds them into
-- the RM winner flow: sets winner, stops active roll, shows award popup.
-------------------------------------------------------------------------------
do
    local RR_winnerFrame = CreateFrame("Frame")
    RR_winnerFrame:RegisterEvent("CHAT_MSG_RAID_WARNING")
    RR_winnerFrame:RegisterEvent("CHAT_MSG_RAID")
    RR_winnerFrame:SetScript("OnEvent", function(self, event, msg)
        if not msg then return end
        -- Strip item link color codes so patterns match cleanly
        local clean = msg:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):gsub("|H[^|]+|h%[(.-)%]|h", "[%1]")

        local winner, roll = nil, 0

        -- Pattern 1: "Name won with 95."  (no item)
        local w, r = clean:match("^(%S+) won with (%d+)%.")
        if w then winner, roll = w, tonumber(r) end

        -- Pattern 2: "Name won [Item] with 95."  (has item)
        if not winner then
            w, r = clean:match("^(%S+) won .+ with (%d+)%.")
            if w then winner, roll = w, tonumber(r) end
        end

        -- Pattern 3: ">>> Name Wins! <<<"
        if not winner then
            w = clean:match("^>>> (%S+) Wins! <<<")
            if w then winner = w end
        end

        -- Pattern 4: "Name won. PR value: X"  (EPGP, no item)
        if not winner then
            w = clean:match("^(%S+) won%. PR value:")
            if w then winner = w end
        end

        -- Pattern 5: "Name won [Item] PR value: X"  (EPGP, with item)
        if not winner then
            w = clean:match("^(%S+) won .+ PR value:")
            if w then winner = w end
        end

        -- Pattern 6: RaidRoll "No winner for [Item]." — treat as no-winner
        if not winner then
            local noWin = clean:match("^No winner for")
            if noWin then
                if RM_recording then
                    RM_cdRunning     = false
                    RM_canRoll       = false
                    RM_recording     = false
                    RM_stopTriggered = false
                    RM_rollAnnouncer = nil
                end
                if RM_rollingItem then
                    local noWinLink = RM_rollingItem.link
                    if noWinLink then
                        RM_SaveRollsToDB(noWinLink, RM_rollLabel)
                        RM_RecordHistory(noWinLink, RM_rollLabel, nil, 0, 0)
                    end
                    for idx = 1, RM_itemCount do
                        if RM_items[idx] == RM_rollingItem then RM_MarkAwarded(idx, "No Winner"); break end
                    end
                elseif RM_selectedIdx and RM_items[RM_selectedIdx] then
                    local noWinLink = RM_items[RM_selectedIdx].link
                    if noWinLink then
                        RM_SaveRollsToDB(noWinLink, RM_rollLabel)
                        RM_RecordHistory(noWinLink, RM_rollLabel, nil, 0, 0)
                    end
                    RM_MarkAwarded(RM_selectedIdx, "No Winner")
                end
                RM_rollingItem = nil
                RM_RefreshRollList()
                RM_RefreshItemList()
                if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
                if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
                DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r RaidRoll: No winner detected.")
                return
            end
        end

        if not winner then return end

        -- Feed winner into RM
        RM_winner = winner

        if RM_recording then
            -- Stop the active roll
            RM_cdRunning     = false
            RM_canRoll       = false
            RM_recording     = false
            RM_stopTriggered = false
            RM_rollAnnouncer = nil
        end

        RM_RefreshRollList()
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end

        -- Show award popup only if we announced this roll or have the loot window open
        -- Otherwise just log to chat — prevents popup spam for uninvolved ML/RL/assists
        if RM_selectedIdx and RM_items[RM_selectedIdx] then
            local item = RM_items[RM_selectedIdx]
            local hasLoot = RM_lootWindowOpen and RM_lootSlotMap[item.link] ~= nil
            if RM_IsAnnouncer() or hasLoot then
                RM_ShowAwardPopup(RM_selectedIdx, winner, roll or 0)
            else
                -- No popup — finalize the award directly (mark done, save history, clear state)
                local rollingLink = item.link
                if rollingLink then
                    RM_SaveRollsToDB(rollingLink, RM_rollLabel)
                    RM_RecordHistory(rollingLink, RM_rollLabel, winner, roll or 0, RM_GetWinnerPosition(winner))
                end
                RM_MarkAwarded(RM_selectedIdx, winner)
                RM_rollingItem = nil
                RM_ClearAndAutoSelect()
                RM_RefreshItemList()
                RM_RefreshRollList()
                if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
                if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
                DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r RaidRoll winner: |cff20ff60" .. winner .. "|r" .. (roll > 0 and (" (" .. roll .. ")") or "") .. " for " .. (rollingLink or item.name or "item"))
            end
        else
            DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r RaidRoll winner detected: |cff20ff60" .. winner .. "|r" .. (roll > 0 and (" (" .. roll .. ")") or ""))
        end
    end)
end

-------------------------------------------------------------------------------
-- Auto-Loot button — appears on LootFrame when ML is disabled
-- Loots all slots that are NOT the currently active rolling item to player bags
-------------------------------------------------------------------------------
local function RM_BuildAutoLootButton()
    if RM_autoLootBtn then return end
    local btn = CreateFrame("Button", "RNToolsAutoLootBtn", UIParent, "UIPanelButtonTemplate")
    btn:SetSize(100, 24)
    btn:SetFrameStrata("TOOLTIP")
    btn:SetText("Auto-Loot")
    -- Apply ElvUI skin if available
    if ElvUI then
        local E = unpack(ElvUI)
        local S = E and E:GetModule("Skins")
        if S and S.HandleButton then pcall(S.HandleButton, S, btn) end
    end
    btn:SetScript("OnClick", function()
        local slotCount = GetNumLootItems()
        if not slotCount or slotCount == 0 then return end

        -- Check in-game loot method directly (no TML dependency)
        local method = GetLootMethod()
        local selfIdx = nil
        if method == "master" then
            local myName = UnitName("player")
            local i = 1
            while true do
                local name = GetMasterLootCandidate(i)
                if not name then break end
                if name == myName then selfIdx = i; break end
                i = i + 1
            end
        end

        local threshold = GetLootThreshold()
        local hasSkipped = false
        -- Iterate in reverse so slot renumbering after each loot doesn't skip items
        for slot = slotCount, 1, -1 do
            local _, _, _, quality = GetLootSlotInfo(slot)
            local itemLink = GetLootSlotLink(slot)
            if itemLink then
                local isRolling = RM_rollingItem and RM_rollingItem.link == itemLink
                if not isRolling then
                    if selfIdx and quality and quality >= threshold then
                        GiveMasterLoot(slot, selfIdx)
                    else
                        LootSlot(slot)
                    end
                else
                    hasSkipped = true
                end
            else
                LootSlot(slot)
            end
        end
        -- Close loot window immediately if nothing was skipped (all items looted)
        if not hasSkipped and LootFrame and LootFrame:IsShown() then
            CloseLoot()
        end
    end)
    btn:Hide()
    RM_autoLootBtn = btn
end

function RM_ShowAutoLootButton()
    if not RM_lootWindowOpen then return end
    RM_BuildAutoLootButton()
    local btn = RM_autoLootBtn
    btn:ClearAllPoints()
    local elvFrame = _G["ElvLootFrame"]
    if elvFrame then
        -- ElvUI custom loot frame: anchor button just above it, centered
        btn:SetPoint("BOTTOMRIGHT", elvFrame, "TOPRIGHT", 0, 14)
    elseif LootFrame and LootFrame.backdrop then
        -- ElvUI skin-only mode: backdrop BOTTOMRIGHT=(-77,+8) relative to LootFrame
        btn:SetPoint("BOTTOMRIGHT", LootFrame, "BOTTOMLEFT", 16, 8 + 30)
    else
        btn:SetPoint("TOP", LootFrame, "BOTTOM", 0, -4)
    end
    btn:Show()
end

-- Countdown ticker
local RM_cdFrame = CreateFrame("Frame")
RM_cdFrame:SetScript("OnUpdate", function(self, elapsed)
    if not RM_cdRunning then return end
    local tick = math.ceil(RM_cdDuration - (GetTime() - RM_cdStart))
    local i = RM_cdPos - 1
    local ch = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
    while i >= tick do
        -- Only announce at 5s if roll wasn't manually stopped
        if i == 5 and RM_IsAnnouncer() and not RM_stopTriggered then
            local raidCh = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
            SendChatMessage("Roll closes in: 5...", raidCh)
        end
        i = i - 1
    end
    RM_cdPos = tick
    if RM_cdPos <= 0 and RM_IsAnnouncer() then
        RM_cdRunning     = false
        RM_canRoll       = false
        RM_recording     = false
        RM_rollingItem   = nil
        RM_stopTriggered = false
        RM_rollAnnouncer = nil
        SendChatMessage("Rolls CLOSED!", ch)
        -- Auto-announce winner (or "no rolls" if nobody rolled)
        if RM_rollCount == 0 then
            local ch = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
            local savedNoWin = RM_selectedIdx
            local link = savedNoWin and RM_items[savedNoWin] and RM_items[savedNoWin].link or "item"
            SendChatMessage(link .. " >> No rolls received. Item skipped.", ch)
            DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r No rolls received for " .. link)
            -- Mark item as skipped BEFORE clearing selection
            if savedNoWin and RM_items[savedNoWin] then
                RM_MarkAwarded(savedNoWin, "No Winner")
            end
            RM_RecordHistory(link, RM_rollLabel, nil, 0, 0)
            RM_recording = false
            RM_ClearAndAutoSelect()
            RM_RefreshItemList()
            if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
            RM_RefreshRollList()
        elseif RM_rollCount > 0 and not RM_winner then
            -- All rolls were ineligible spec — treat as no winner
            local link = RM_selectedIdx and RM_items[RM_selectedIdx] and RM_items[RM_selectedIdx].link or "item"
            local ch2 = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
            SendChatMessage(link .. " >> No eligible rolls received. Item skipped.", ch2)
            DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r No eligible rolls for " .. link)
            local savedNoElig = RM_selectedIdx
            if savedNoElig and RM_items[savedNoElig] then RM_MarkAwarded(savedNoElig, "No Winner") end
            RM_RecordHistory(link, RM_rollLabel, nil, 0, 0)
            RM_ClearAndAutoSelect()
            RM_RefreshItemList()
            if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        elseif RM_winner and RM_rollCount > 0 then
            local link     = RM_selectedIdx and RM_items[RM_selectedIdx] and RM_items[RM_selectedIdx].link or "item"
            local topRoll  = 0
            for _, e in ipairs(RM_rolls) do if e.name == RM_winner then topRoll = e.roll; break end end
            local myName   = UnitName("player")
            local cdWinPos = RM_GetWinnerPosition(RM_winner)  -- declare before use
            local rwMsg    = link .. " >> Winner: " .. RM_winner ..
                             " (" .. topRoll .. " - " .. RM_rollLabel ..
                             " #" .. cdWinPos ..
                             "). Trade " .. myName .. " to receive your item."
            -- /raid for winner (not /rw to avoid parser loop)
            local rwCh = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
            SendChatMessage(rwMsg, rwCh)
            -- Whisper the winner
            if RM_winner ~= myName then
                SendChatMessage("Congratulations! You won " .. link ..
                    " (" .. RM_rollLabel .. " roll - " .. topRoll ..
                    " #" .. cdWinPos ..
                    "). Trade " .. myName .. " to receive your item.", "WHISPER", nil, RM_winner)
            end
            -- Mark item awarded with winner name
            local _savedIdx2 = RM_selectedIdx
            if _savedIdx2 and RM_items[_savedIdx2] then
                RM_SaveRollsToDB(link, RM_rollLabel)
                RM_MarkAwarded(_savedIdx2, RM_winner)
                RM_RecordHistory(link, RM_rollLabel, RM_winner, topRoll, cdWinPos)
            end
            RM_ClearAndAutoSelect()
            DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r Auto-awarded " ..
                link .. " to |cff20ff60" .. RM_winner .. "|r")
            RM_ClearRolls()
            RM_selectedIdx = nil
            if RM_selectedFS then RM_selectedFS:SetText("|cff666666No item selected|r") end
            RM_RefreshItemList()
            if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        end
        RM_RefreshRollList()
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
    end
end)

-- ── UI Builder ────────────────────────────────────────────────────────

-- Non-RL 45-second timeout: if RL/ML never announces CLOSED, auto-stop after 45s
local RM_nonRL_timeoutEnd = nil
local RM_timeoutWatcher = CreateFrame("Frame")
RM_timeoutWatcher:SetScript("OnUpdate", function()
    if not RM_nonRL_timeoutEnd then return end
    if GetTime() < RM_nonRL_timeoutEnd then return end
    RM_nonRL_timeoutEnd = nil
    if RM_recording and not RM_HasPermission() then
        RM_recording = false
        RM_canRoll   = false
        -- Mark the rolling item as timed out only if not already resolved (e.g. No Winner arrived)
        if RM_rollingItem then
            for idx = 1, RM_itemCount do
                if RM_items[idx] == RM_rollingItem then
                    if not RM_items[idx].awarded then
                        RM_MarkAwarded(idx, "Timeout")
                    end
                    break
                end
            end
        end
        RM_rollingItem = nil
        DEFAULT_CHAT_FRAME:AddMessage("|cff888888[Roll Manager]:|r Roll timeout (45s) — no CLOSED received.")
        RM_RefreshRollList()
        RM_RefreshItemList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
    end
end)

local function RM_nonRL_StopRecording()
    RM_nonRL_timeoutEnd = nil
    RM_recording   = false
    RM_canRoll     = false
    RM_rollingItem = nil
end

-- Non-RL end-of-roll handler: listens to RAID chat for winner/closed/roll-start
-- Also catches RaidRoll-style announcements (item link + "roll" keyword in message)
local RM_nonRL_frame = CreateFrame("Frame")
RM_nonRL_frame:RegisterEvent("CHAT_MSG_RAID")
RM_nonRL_frame:RegisterEvent("CHAT_MSG_RAID_LEADER")
RM_nonRL_frame:RegisterEvent("CHAT_MSG_RAID_WARNING")
RM_nonRL_frame:RegisterEvent("CHAT_MSG_PARTY")
RM_nonRL_frame:RegisterEvent("CHAT_MSG_PARTY_LEADER")
RM_nonRL_frame:RegisterEvent("CHAT_MSG_SAY")
RM_nonRL_frame:SetScript("OnEvent", function(self, event)
    if RM_IsAnnouncer() then return end  -- only skip if WE opened this roll
    local msg = arg1
    if not msg then return end
    local low = string.lower(msg)

    -- "Rolls CLOSED!" or RaidRoll's "Finishing Rolling Early" -> stop accepting new rolls,
    -- but keep RM_rollingItem alive so a follow-up winner/no-winner message can mark the item done.
    if string.find(low, "rolls closed") or string.find(low, "finishing rolling early") then
        RM_nonRL_timeoutEnd = nil
        RM_recording = false
        RM_canRoll   = false
        RM_RefreshRollList()
        RM_RefreshItemList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        return
    end

    -- "[item] >> Winner: Name (...)" -> mark item awarded
    if string.find(low, ">> winner:") then
        local winnerName = msg:match(">> Winner: ([^%(]+)")
        if winnerName then winnerName = winnerName:match("^%s*(.-)%s*$") end
        if winnerName and winnerName ~= "" then
            RM_winner = winnerName
            local targetIdx = nil
            if RM_rollingItem then
                for idx = 1, RM_itemCount do
                    if RM_items[idx] == RM_rollingItem then targetIdx = idx; break end
                end
            end
            if not targetIdx then
                local itemID = msg:match("item:(%d+)")
                if itemID then
                    for idx = 1, RM_itemCount do
                        if RM_items[idx] and RM_items[idx].link and
                           RM_items[idx].link:find("item:"..itemID, 1, true) then
                            targetIdx = idx; break
                        end
                    end
                end
            end
            if targetIdx then
                local rollingLink = RM_items[targetIdx].link
                RM_SaveRollsToDB(rollingLink, RM_rollLabel)
                RM_MarkAwarded(targetIdx, winnerName)
                local winRoll, winPos = 0, 1
                for i = 1, RM_rollCount do
                    if RM_rolls[i] and RM_rolls[i].name == winnerName then
                        winRoll = RM_rolls[i].roll; winPos = i; break
                    end
                end
                RM_RecordHistory(rollingLink, RM_rollLabel, winnerName, winRoll, winPos)
            end
        end
        RM_nonRL_StopRecording()
        RM_RefreshItemList()
        RM_RefreshRollList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        return
    end

    -- "No rolls received" / "Item skipped" / "No eligible rolls" / RaidRoll's "No winner for" -> mark as No Winner
    local isNoWinner = string.find(low, "no rolls received")
        or string.find(low, "item skipped")
        or string.find(low, "no eligible rolls")
        or string.find(low, "no winner for")
    if isNoWinner then
        if RM_rollingItem then
            local noWinLink = RM_rollingItem.link
            if noWinLink then
                RM_SaveRollsToDB(noWinLink, RM_rollLabel)
                RM_RecordHistory(noWinLink, RM_rollLabel, nil, 0, 0)
            end
            for idx = 1, RM_itemCount do
                if RM_items[idx] == RM_rollingItem then RM_MarkAwarded(idx, "No Winner"); break end
            end
        end
        RM_nonRL_timeoutEnd = nil  -- cancel timeout — already resolved
        RM_nonRL_StopRecording()
        RM_RefreshItemList()
        RM_RefreshRollList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        return
    end

    -- RaidRoll-style detection: item link + "roll" in surrounding text → start tracking
    -- Mirrors RaidRoll addon logic: strip the item link, check if "roll" appears in the rest
    if not RM_recording and string.find(msg, "item:") and string.find(msg, "|h|r") then
        local linkStart = string.find(low, "item:")
        local _, linkEnd = string.find(msg, "|h|r")
        if linkStart and linkEnd then
            local before = string.lower(string.sub(msg, 1, math.max(1, linkStart - 12)))
            local after  = string.lower(string.sub(msg, linkEnd + 1))
            if string.find(before .. after, "roll") then
                local itemID = msg:match("item:(%d+)")
                if itemID then
                    for idx = 1, RM_itemCount do
                        if RM_items[idx] and not RM_items[idx].awarded and
                           RM_items[idx].link and RM_items[idx].link:find("item:"..itemID, 1, true) then
                            RM_selectedIdx    = idx
                            RM_rollingItem    = RM_items[idx]
                            RM_recording      = true
                            RM_canRoll        = true
                            RM_nonRL_timeoutEnd = GetTime() + 45  -- 45s timeout if no CLOSED
                            RM_ClearRolls()
                            RM_RefreshItemList()
                            if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
                            break
                        end
                    end
                end
            end
        end
    end
end)

-- Global stop roll function - callable from mini RM or anywhere
function RM_StopRoll()
    if not RM_cdRunning and not RM_recording then return end
    if RM_HasPermission() then
        if RM_cdDuration and RM_cdDuration == 0 then
            -- Manual-close mode: announce + award just like timed mode
            RM_cdRunning     = false
            RM_canRoll       = false
            RM_recording     = false
            RM_stopTriggered = false

            if RM_IsAnnouncer() then
                local ch = GetNumRaidMembers() > 0 and "RAID" or "PARTY"
                SendChatMessage("Rolls CLOSED!", ch)

                if RM_rollCount == 0 then
                    local link = RM_selectedIdx and RM_items[RM_selectedIdx] and RM_items[RM_selectedIdx].link or "item"
                    SendChatMessage(link .. " >> No rolls received. Item skipped.", ch)
                    DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r No rolls received for " .. link)
                    if RM_selectedIdx and RM_items[RM_selectedIdx] then
                        RM_MarkAwarded(RM_selectedIdx, "No Winner")
                    end
                    RM_RecordHistory(link, RM_rollLabel, nil, 0, 0)
                    RM_ClearAndAutoSelect()
                    RM_RefreshItemList()
                    if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
                elseif RM_rollCount > 0 and not RM_winner then
                    local link = RM_selectedIdx and RM_items[RM_selectedIdx] and RM_items[RM_selectedIdx].link or "item"
                    SendChatMessage(link .. " >> No eligible rolls received. Item skipped.", ch)
                    DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r No eligible rolls for " .. link)
                    if RM_selectedIdx and RM_items[RM_selectedIdx] then RM_MarkAwarded(RM_selectedIdx, "No Winner") end
                    RM_RecordHistory(link, RM_rollLabel, nil, 0, 0)
                    RM_ClearAndAutoSelect()
                    RM_RefreshItemList()
                    if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
                elseif RM_winner and RM_rollCount > 0 then
                    local link    = RM_selectedIdx and RM_items[RM_selectedIdx] and RM_items[RM_selectedIdx].link or "item"
                    local topRoll = 0
                    for _, e in ipairs(RM_rolls) do if e.name == RM_winner then topRoll = e.roll; break end end
                    local myName  = UnitName("player")
                    local winPos  = RM_GetWinnerPosition(RM_winner)
                    local p2 = RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile
                    local winTmpl = (p2 and p2.rm_winner_tmpl) or "{item} >> Winner: {name} ({roll} - {label})"
                    local rwMsg = winTmpl:gsub("{item}", link):gsub("{name}", RM_winner):gsub("{roll}", tostring(topRoll)):gsub("{label}", RM_rollLabel)
                    SendChatMessage(rwMsg, ch)
                    if RM_winner ~= myName then
                        SendChatMessage("Congratulations! You won " .. link ..
                            " (" .. RM_rollLabel .. " roll - " .. topRoll ..
                            " #" .. winPos ..
                            "). Trade " .. myName .. " to receive your item.", "WHISPER", nil, RM_winner)
                    end
                    if RM_selectedIdx and RM_items[RM_selectedIdx] then
                        RM_SaveRollsToDB(link, RM_rollLabel)
                        RM_MarkAwarded(RM_selectedIdx, RM_winner)
                        RM_RecordHistory(link, RM_rollLabel, RM_winner, topRoll, winPos)
                    end
                    DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r Awarded " ..
                        link .. " to |cff20ff60" .. RM_winner .. "|r")
                    RM_ClearAndAutoSelect()
                    RM_ClearRolls()
                    RM_RefreshItemList()
                    if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
                end
                RM_rollingItem   = nil
                RM_rollAnnouncer = nil
            end

            RM_RefreshRollList()
            if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        else
            -- Timed mode: force countdown end immediately, skip 5sec announce
            RM_stopTriggered = true
            RM_cdRunning     = true
            RM_cdStart       = GetTime() - RM_cdDuration - 1
            RM_cdPos         = RM_cdDuration + 1
        end
    end
end

function RM_BuildUI(parentFrame)
    local PANEL_X = 548
    local PANEL_Y = -34
    local PANEL_W = 540   -- 1100 - 535 - 20 right margin = 535
    local PANEL_H = 493
    local P       = 8

    -- Vertical divider hidden — RM panel no longer shown in main tab
    local mlDivider = parentFrame:CreateTexture(nil, "ARTWORK")
    mlDivider:Hide()

    -- Outer panel
    local panel = CreateFrame("Frame", nil, parentFrame)
    panel:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PANEL_X, PANEL_Y)
    panel:SetSize(PANEL_W, PANEL_H)
    panel:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",
        edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1,
        insets={left=1,right=1,top=1,bottom=1}})
    panel:SetBackdropColor(0.08,0.08,0.08,0.97)
    panel:SetBackdropBorderColor(0.55,0.50,0.42,0.8)
    RM_panel = panel

    -- Divide panel: top half = item list (~180px), bottom half = rolls (~270px)
    local ITEM_SECTION_H = 180
    local ROLL_SECTION_H = PANEL_H - ITEM_SECTION_H - P*3 - 60  -- 60 for bottom buttons

    local y = -P

    -- ── ITEM LIST SECTION ────────────────────────────────────────────
    RMHdr(panel, "Loot  (click to select item for roll)", P, y, PANEL_W - P*2)
    y = y - 24

    -- Selected item display
    RM_selectedFS = panel:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    RM_selectedFS:SetPoint("TOPLEFT", panel, "TOPLEFT", P+3, y)
    RM_selectedFS:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -P-3, y)
    RM_selectedFS:SetText("|cff666666No item selected|r")
    RM_selectedFS:SetNonSpaceWrap(false)
    y = y - 18

    -- Sort header row: [Item Name] ... [Time Left]
    -- [P+3] "Item Name" click=sort alpha  |  "Time Left" click=sort time [P+3]
    local HDR_H = 18
    local sortNameBtn = RMBtn(panel, "Item Name  ↕", 120, HDR_H)
    sortNameBtn:SetPoint("TOPLEFT", panel, "TOPLEFT", P+3, y)
    sortNameBtn._fs:SetTextColor(unpack({0.85,0.75,0.55,1}))
    sortNameBtn:SetScript("OnClick", function()
        if RM_sortBy == "name_asc" then
            RM_sortBy = "name_desc"
            sortNameBtn._fs:SetText("Item Name  ↓")
        else
            RM_sortBy = "name_asc"
            sortNameBtn._fs:SetText("Item Name  ↑")
        end
        RM_RefreshItemList()
    end)

    local sortTimeBtn = RMBtn(panel, "Time Left  ↕", 110, HDR_H)
    sortTimeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -P-18-3, y)
    sortTimeBtn._fs:SetTextColor(unpack({0.85,0.75,0.55,1}))
    sortTimeBtn:SetScript("OnClick", function()
        if RM_sortBy == "time_asc" then
            RM_sortBy = "time_desc"
            sortTimeBtn._fs:SetText("Time Left  ↓")
        else
            RM_sortBy = "time_asc"
            sortTimeBtn._fs:SetText("Time Left  ↑")
        end
        RM_RefreshItemList()
    end)
    y = y - (HDR_H + 2)

    -- Item scroll frame
    local itemSF = CreateFrame("ScrollFrame", "RNRollItemSF", panel, "UIPanelScrollFrameTemplate")
    itemSF:SetPoint("TOPLEFT",     panel, "TOPLEFT",     P,     y)
    itemSF:SetPoint("TOPRIGHT",    panel, "TOPRIGHT",    -P-18, y)
    itemSF:SetHeight(ITEM_SECTION_H - HDR_H - 2)
    itemSF:SetScript("OnSizeChanged", function(s,w) if w and w>10 then RM_itemParent:SetWidth(w) end end)
    itemSF:SetScript("OnScrollRangeChanged", function(self, xr, yr)
        local bar  = _G["RNRollItemSFScrollBar"]
        if not bar then return end
        yr = math.floor(yr+0.5)
        bar:SetMinMaxValues(0,yr); bar:SetValue(math.min(bar:GetValue(),yr))
        local up   = _G["RNRollItemSFScrollBarScrollUpButton"]
        local down = _G["RNRollItemSFScrollBarScrollDownButton"]
        local v    = bar:GetValue()
        if up   then if v>0  then up:Enable()   else up:Disable()   end end
        if down then if v<yr then down:Enable() else down:Disable() end end
    end)
    local itemSC = CreateFrame("Frame", nil, itemSF)
    itemSC:SetHeight(800)
    itemSF:SetScrollChild(itemSC)
    RM_itemParent = itemSC

    -- "No permission" notice - shown when not RL/ML/Assist and no items
    RM_noPermFS = panel:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    RM_noPermFS:SetPoint("CENTER", itemSF, "CENTER", 0, 0)
    RM_noPermFS:SetTextColor(0.5,0.5,0.5,1)
    RM_noPermFS:SetText("|cffFF8C00Roll Manager: Raid Leader / Master Looter / Assist only|r")
    RM_noPermFS:Hide()

    y = y - ITEM_SECTION_H - P

    -- ── ROLL TYPE BUTTONS ────────────────────────────────────────────
    RMHdr(panel, "Start Roll", P, y, PANEL_W - P*2)
    y = y - 28

    -- [P] MS [3px] OS [3px] Free [8px] Countdown [3px] Stop [P]
    local msBtn = RMBtn(panel, "|cff20ff60MS|r", 68, 24)
    msBtn:SetPoint("TOPLEFT", panel, "TOPLEFT", P+3, y)
    msBtn:SetScript("OnClick", function() RM_AnnounceRoll("MS") end)

    local osBtn = RMBtn(panel, "|cffFFD100OS|r", 68, 24)
    osBtn:SetPoint("LEFT", msBtn, "RIGHT", 3, 0)
    osBtn:SetScript("OnClick", function() RM_AnnounceRoll("OS") end)

    local freeBtn = RMBtn(panel, "|cffaaaaFFFree|r", 68, 24)
    freeBtn:SetPoint("LEFT", osBtn, "RIGHT", 3, 0)
    freeBtn:SetScript("OnClick", function() RM_AnnounceRoll("Free") end)

    -- Initial permission state (will be updated by OnUpdate)
    if not RM_HasPermission() then
        msBtn:Disable(); osBtn:Disable(); freeBtn:Disable()
    end

    local stopBtn = RMBtn(panel, "Stop Roll", 78, 24)
    stopBtn:SetPoint("LEFT", freeBtn, "RIGHT", 8, 0)
    stopBtn:SetBackdropColor(0.22,0.04,0.04,1)
    stopBtn:SetBackdropBorderColor(0.80,0.20,0.10,1)
    stopBtn._fs:SetTextColor(1,0.4,0.4,1)
    stopBtn:SetScript("OnClick", function()
        RM_StopRoll()
    end)

    -- CD duration dropdown - unique name to avoid conflict with main RM frame
    local RM_CDDDMenu = CreateFrame("Frame", "RNMiniRMCDDDMenu", UIParent, "UIDropDownMenuTemplate")
    local cdOpenBtn   = nil
    local cdBtn2 = RMBtn(panel, "CD: "..RM_cdDuration.."s", 72, 24)
    RM_cdBtnPanel = cdBtn2
    cdBtn2:SetPoint("LEFT", stopBtn, "RIGHT", 6, 0)
    cdBtn2._fs:SetTextColor(1,0.82,0,1)
    cdBtn2:SetScript("OnClick", function(self)
        if cdOpenBtn == self then
            CloseDropDownMenus(); cdOpenBtn = nil; return
        end
        cdOpenBtn = self
        local menu = {}
        for _, secs in ipairs({0,10,15,20,25,30}) do
            local capSecs = secs
            menu[#menu+1] = {
                text    = capSecs == 0 and "(none)" or capSecs.." sec",
                checked = (RM_cdDuration == capSecs),
                func    = function()
                    RM_cdDuration = capSecs
                    cdOpenBtn = nil
                    if type(RTRollManagerSave) ~= "table" then RTRollManagerSave = {} end
                    RTRollManagerSave.cdDuration = capSecs
                    RNTools_SyncCDLabel()
                    local msg = capSecs == 0 and "Countdown disabled — close rolls manually." or "Countdown set to "..capSecs.." seconds."
                    DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[Roll Manager]:|r "..msg)
                end,
            }
        end
        EasyMenu(menu, RM_CDDDMenu, self, 0, 0, "MENU")
    end)

    y = y - 32

    -- RL/ML status indicator
    local permFS = panel:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    permFS:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -P-3, y)
    if RM_HasPermission() then
        permFS:SetText("|cff20ff60You are ML/RL|r")
    else
        permFS:SetText("|cffff4444You are not RL/ML|r")
    end
    -- Refresh every 2s in case role changes
    local permTick = 0
    panel:SetScript("OnUpdate", function(self, elapsed)
        permTick = permTick + elapsed
        if permTick < 2 then return end
        permTick = 0
        if RM_HasPermission() then
            permFS:SetText("|cff20ff60You are ML/RL|r")
        else
            permFS:SetText("|cffff4444You are not RL/ML|r")
        end
        -- Enable/disable buttons based on permission (nil-safe)
        if RM_HasPermission() then
            if msBtn then msBtn:Enable() end
            if osBtn then osBtn:Enable() end
            if freeBtn then freeBtn:Enable() end
            if stopBtn then stopBtn:Enable() end
            if awardBtn then awardBtn:Enable() end
        else
            if msBtn then msBtn:Disable() end
            if osBtn then osBtn:Disable() end
            if freeBtn then freeBtn:Disable() end
            if stopBtn then stopBtn:Disable() end
            if awardBtn then awardBtn:Disable() end
        end
    end)

    -- Status + count
    RM_statusFS = panel:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    RM_statusFS:SetPoint("TOPLEFT", panel, "TOPLEFT", P+3, y)
    RM_statusFS:SetText("|cff666666Idle — select an item and start a roll|r")
    y = y - 18

    RM_countFS = panel:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    RM_countFS:SetPoint("TOPLEFT", panel, "TOPLEFT", P+3, y)
    RM_countFS:SetTextColor(0.60,0.60,0.60,1); RM_countFS:SetText("0 rolls")
    y = y - 22

    -- ── ROLL LIST ────────────────────────────────────────────────────
    RMHdr(panel, "Rolls  (click name to select winner)", P, y, PANEL_W - P*2)
    y = y - 24

    local rollSF = CreateFrame("ScrollFrame", "RNRollListSF", panel, "UIPanelScrollFrameTemplate")
    rollSF:SetPoint("TOPLEFT",     panel, "TOPLEFT",     P,     y)
    rollSF:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -P-18, 36)
    rollSF:SetScript("OnSizeChanged", function(s,w) if w and w>10 then RM_listParent:SetWidth(w) end end)
    rollSF:SetScript("OnScrollRangeChanged", function(self, xr, yr)
        local bar = _G["RNRollListSFScrollBar"]
        if not bar then return end
        yr = math.floor(yr+0.5)
        bar:SetMinMaxValues(0,yr); bar:SetValue(math.min(bar:GetValue(),yr))
        local up   = _G["RNRollListSFScrollBarScrollUpButton"]
        local down = _G["RNRollListSFScrollBarScrollDownButton"]
        local v    = bar:GetValue()
        if up   then if v>0  then up:Enable()   else up:Disable()   end end
        if down then if v<yr then down:Enable() else down:Disable() end end
    end)
    local rollSC = CreateFrame("Frame", nil, rollSF)
    rollSC:SetHeight(800)
    rollSF:SetScrollChild(rollSC)
    RM_listParent = rollSC

    -- ── BOTTOM ACTION BUTTONS ────────────────────────────────────────
    -- [P] Clear [3px] Award [P]  (anchored to panel bottom)
    local clearBtn = RMBtn(panel, "Clear Rolls", 110, 26)
    clearBtn:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", P+3, P+3)
    clearBtn._fs:SetTextColor(1,0.4,0.4,1)
    clearBtn:SetScript("OnClick", function() RM_ClearRolls() end)

    local awardBtn = RMBtn(panel, "Award Winner", 120, 26)
    awardBtn:SetPoint("LEFT", clearBtn, "RIGHT", 3, 0)
    awardBtn._fs:SetTextColor(0.20,1.00,0.40,1)
    awardBtn:SetScript("OnClick", function()
        if not RM_winner then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Roll Manager]:|r No winner selected.")
            return
        end
        if not RM_selectedIdx or not RM_items[RM_selectedIdx] then return end
        local roll = 0
        for _, entry in ipairs(RM_rolls) do if entry.name == RM_winner then roll = entry.roll; break end end
        RM_ShowAwardPopup(RM_selectedIdx, RM_winner, roll)
    end)

    local clearItemBtn = RMBtn(panel, "Clear Items", 90, 26)
    clearItemBtn:SetPoint("LEFT", awardBtn, "RIGHT", 6, 0)
    clearItemBtn._fs:SetTextColor(0.85,0.75,0.55,1)
    clearItemBtn:SetScript("OnClick", function()
        RM_ClearItems()
        RM_RefreshItemList()
    end)

    local histItemBtn = RMBtn(panel, "History", 76, 26)
    histItemBtn:SetPoint("LEFT", clearItemBtn, "RIGHT", 6, 0)
    histItemBtn._fs:SetTextColor(1,0.82,0,1)
    histItemBtn:SetScript("OnClick", function()
        if RNTools_ToggleHistory then RNTools_ToggleHistory() end
    end)

    RM_RefreshRollList()
    RM_RefreshItemList()
end

-------------------------------------------------------------------------------
-- RNTools Roll Announcer Parser
-- Listens to CHAT_MSG_RAID_WARNING for item link + roll keyword.
-- Mirrors RaidRoll detection exactly. Only /rw channel.
-------------------------------------------------------------------------------

local RM_parserFrame = CreateFrame("Frame")
RM_parserFrame:RegisterEvent("CHAT_MSG_RAID_WARNING")
RM_parserFrame:SetScript("OnEvent", function(self, event)
    if event ~= "CHAT_MSG_RAID_WARNING" then return end

    local msg = arg1
    if not msg or msg == "" then return end

    -- Lowercase for pattern matching
    local low = string.lower(msg)

    -- "Rolls CLOSED!" or RaidRoll's "Finishing Rolling Early" — stop accepting rolls
    -- but keep RM_rollingItem alive so the follow-up winner/no-winner message can mark the item
    if string.find(low, "rolls closed") or string.find(low, "finishing rolling early") then
        if not RM_IsAnnouncer() then
            RM_recording = false
            RM_canRoll   = false
            RM_RefreshRollList()
            if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        end
        return
    end

    -- RaidRoll "No winner for [Item]." — mark item as No Winner for spectators
    if string.find(low, "no winner for") and not RM_IsAnnouncer() then
        local savedItem = RM_rollingItem
        RM_recording     = false
        RM_canRoll       = false
        RM_rollingItem   = nil
        RM_rollAnnouncer = nil
        if savedItem then
            local noWinLink = savedItem.link
            if noWinLink then
                RM_SaveRollsToDB(noWinLink, RM_rollLabel)
                RM_RecordHistory(noWinLink, RM_rollLabel, nil, 0, 0)
            end
            for idx = 1, RM_itemCount do
                if RM_items[idx] == savedItem then RM_MarkAwarded(idx, "No Winner"); break end
            end
        end
        RM_RefreshItemList()
        RM_RefreshRollList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        return
    end

    -- RNTools "No rolls received" / "No eligible rolls" — same flow for spectators
    if (string.find(low, "no rolls received") or string.find(low, "no eligible rolls")) and not RM_IsAnnouncer() then
        local savedItem = RM_rollingItem
        RM_recording     = false
        RM_canRoll       = false
        RM_rollingItem   = nil
        RM_rollAnnouncer = nil
        if savedItem then
            for idx = 1, RM_itemCount do
                if RM_items[idx] == savedItem then RM_MarkAwarded(idx, "No Winner"); break end
            end
        end
        RM_RefreshItemList()
        RM_RefreshRollList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        return
    end

    -- Winner announcement: parse winner name and mark item awarded for spectators
    if string.find(low, ">> winner:") and not RM_IsAnnouncer() then
        -- Format: "link >> Winner: NAME (roll - Label #pos). Trade ..."
        local winnerName = msg:match(">> Winner: ([^%(]+)%(")
        if winnerName then
            winnerName = winnerName:match("^%s*(.-)%s*$")  -- trim whitespace
        end
        local savedItem = RM_rollingItem
        RM_recording     = false
        RM_canRoll       = false
        RM_rollingItem   = nil
        RM_rollAnnouncer = nil
        if winnerName and savedItem then
            -- Find and mark the item awarded
            for idx = 1, RM_itemCount do
                if RM_items[idx] == savedItem then
                    RM_MarkAwarded(idx, winnerName)
                    break
                end
            end
        end
        RM_RefreshItemList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        return
    end

    -- No rolls / no eligible rolls: mark item as skipped for spectators
    if (string.find(low, "no rolls received") or string.find(low, "no eligible rolls")) and not RM_IsAnnouncer() then
        local savedItem = RM_rollingItem
        RM_recording     = false
        RM_canRoll       = false
        RM_rollingItem   = nil
        RM_rollAnnouncer = nil
        if savedItem then
            for idx = 1, RM_itemCount do
                if RM_items[idx] == savedItem then
                    RM_MarkAwarded(idx, "No Winner")
                    break
                end
            end
        end
        RM_RefreshItemList()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
        return
    end

    -- Skip if already handled above or not relevant
    if string.find(low, ">> winner:")        then return end
    if string.find(low, "no rolls received") then return end
    if string.find(low, "no eligible rolls") then return end

    -- Must contain an item link
    if not string.find(low, "item:") then return end
    if not string.find(low, "|h|r")  then return end

    -- Find item link boundaries (same approach as RaidRoll)
    local startLoc = string.find(low, "item:")
    local endLoc   = string.find(low, "|h|r")
    if not startLoc or not endLoc then return end

    -- Find the real start of the link: look for |H before "item:" or |c before that
    -- Colour link format: |cffXXXXXX|Hitem:...|h[Name]|h|r  (12 chars before item:)
    -- Plain link format:  |Hitem:...|h[Name]|h|r              (2 chars before item:)
    local linkStart = startLoc - 2   -- default: |H prefix only
    if linkStart >= 12 then
        local colorCheck = string.sub(msg, startLoc - 12, startLoc - 1)
        if string.find(colorCheck, "|c") then
            linkStart = startLoc - 12
        end
    end
    if linkStart < 1 then linkStart = 1 end
    local rawLink = string.sub(msg, linkStart, endLoc)

    -- Text around the link (before + after), lowercase
    local before = string.lower(string.sub(msg, 1, linkStart - 1))
    local after  = string.lower(string.sub(msg, endLoc + 1))
    local around = before .. " " .. after

    -- RaidRoll behaviour: any /rw with an item link starts a roll,
    -- even without the word "roll" in the message.
    -- For non-/rw channels the "roll" keyword is still required (handled by RM_nonRL_frame).

    -- Get the proper full coloured item link via item ID
    local itemID = tonumber(string.match(rawLink, "item:(%d+)"))
    local itemLink = rawLink  -- fallback to raw
    if itemID then
        local cleanLink = select(2, GetItemInfo(itemID))
        if cleanLink then itemLink = cleanLink end
    end

    -- Detect roll type
    local rollType = "MS"
    if string.find(around, " os ") or string.find(around, " os$")
    or string.find(around, "^os ") or string.find(around, "off.spec")
    or string.find(around, "offspec") then
        rollType = "OS"
    elseif string.find(around, "free") then
        rollType = "Free"
    end

    -- Extract item name from [Name] inside link
    local itemName = string.match(itemLink, "%[(.-)%]") or "Unknown"

    -- Find or add item to RM_items
    local foundIdx = nil
    for i = 1, RM_itemCount do
        if RM_items[i] and RM_items[i].link == itemLink then
            foundIdx = i; break
        end
    end

    if not foundIdx then
        local _, _, quality, _, _, _, _, _, _, texture = GetItemInfo(itemLink)
        quality = quality or 4
        texture = texture or "Interface\\Icons\\INV_Misc_QuestionMark"
        RM_itemCount = RM_itemCount + 1
        RM_items[RM_itemCount] = {
            link = itemLink, name = itemName,
            texture = texture, quality = quality,
        }
        foundIdx = RM_itemCount
    end

    -- Select the item
    RM_selectedIdx = foundIdx
    if RM_selectedFS then
        RM_selectedFS:SetText("Item: " .. itemLink)
    end

    -- Clear old rolls and reset state for new roll
    RM_ClearRolls()

    -- Un-award item if it was previously marked Done (re-roll scenario)
    if RM_items[foundIdx] and RM_items[foundIdx].awarded then
        RM_items[foundIdx].awarded = false
        RM_items[foundIdx].winner  = nil
    end

    -- Always start recording (reset state regardless of previous roll)
    RM_rollLabel     = rollType
    RM_recording     = true
    RM_canRoll       = true
    RM_rollingItem   = RM_items[foundIdx]
    RM_rerolled      = {}
    RM_winner        = nil
    RM_rollAnnouncer = arg2  -- track who sent the /rw (arg2 = sender of CHAT_MSG_RAID_WARNING)
    -- Only the announcer runs the countdown timer
    if RM_IsAnnouncer() and RM_cdDuration and RM_cdDuration > 0 then
        RM_cdRunning = true
        RM_cdStart   = GetTime()
        RM_cdPos     = RM_cdDuration + 1
    end

    if RM_RefreshItemList then RM_RefreshItemList() end
    if RM_RefreshRollList then RM_RefreshRollList() end

    -- After sort, re-find selectedIdx by reference (sort may have moved item)
    if RM_rollingItem then
        for i = 1, RM_itemCount do
            if RM_items[i] == RM_rollingItem then
                RM_selectedIdx = i
                break
            end
        end
    end

    -- Open Mini Roll Manager FIRST (this registers RNTools_SetMiniSelection)
    -- Respects the "Auto-show Mini Roll Manager" config toggle (rm_mini_auto_show).
    -- When disabled the window stays hidden but all roll tracking continues normally.
    if BuildMiniRollManager and (RM_MiniAutoShow ~= false) then BuildMiniRollManager() end

    -- Sync mini RM selection AFTER window is built
    if RNTools_SetMiniSelection then
        RNTools_SetMiniSelection(RM_rollingItem)
    end

    -- Refresh item list so Rolling... indicator shows immediately
    RM_RefreshItemList()
    if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end

    DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[RNTools]:|r " ..
        rollType .. " roll detected: " .. itemLink)
end)

-- Global stub so parser can call BuildMiniRollManager before UI_Core loads it
-- (always opens on demand — the auto-show flag only suppresses the automatic trigger)
function RNTools_OpenMiniRollManager()
    if BuildMiniRollManager then BuildMiniRollManager() end
end

-- Initialise RM_MiniAutoShow from the saved profile so the flag is available
-- before Config.lua's afterFn fires.  Defaults to true (auto-show enabled).
local _miniAutoInitFrame = CreateFrame("Frame")
_miniAutoInitFrame:RegisterEvent("PLAYER_LOGIN")
_miniAutoInitFrame:SetScript("OnEvent", function()
    if _G.RNToolsAddon and _G.RNToolsAddon.db then
        local val = _G.RNToolsAddon.db.profile.rm_mini_auto_show
        _G.RM_MiniAutoShow = (val ~= false)  -- nil → true (default on)
    end
    _miniAutoInitFrame:UnregisterEvent("PLAYER_LOGIN")
end)
