local addonName, ns = ...

-- ============================================================
-- Constants & Addon Prefix
-- ============================================================
local PREFIX = "BBIGAMBLE"
local MUSIC_FILE = "Interface\\AddOns\\BrosBeforeIOsGambling\\MUS_1110_Drive_6684200.mp3"
--- Copy of VO_111_Xalatath_Blade_of_the_Black_Empire_06_F.ogg in the addon folder (custom asset).
local SOUND_LOSE_STAKE = "Interface\\AddOns\\BrosBeforeIOsGambling\\VO_111_Xalatath_Blade_of_the_Black_Empire_06_F.ogg"
C_ChatInfo.RegisterAddonMessagePrefix(PREFIX)

local function PlayLoseStakeSound()
    PlaySoundFile(SOUND_LOSE_STAKE, "SFX")
end

-- ============================================================
-- State
-- ============================================================
local Players = {}           -- { {name, hasRolled}, ... }
local Rolls = {}             -- { {name, value}, ... }
local Channel = "RAID"
local ChannelIndex = 2
local CurrentRollValue = 1000
local Locked = false
local GameActive = false
local TestMode = false
local Me

--- In-memory only; reset each login. Mirrored alongside BBIGStats on each result (all-time log).
local SessionStats = {All = {}, Specific = {}}

local CHANNELS = {"PARTY", "RAID", "GUILD"}
local CHANNEL_LABELS = {
    PARTY = format("|cffaaaaff%s|r", "Party"),
    RAID  = format("|cffff8000%s|r", "Raid"),
    GUILD = format("|cff40ff40%s|r", "Guild"),
}
local ROLL_PATTERN = "^(%S+)%s%S+%s(%d+)%s%((%d+)-(%d+)%)"
local CHAT_EVENTS = {
    PARTY = {"CHAT_MSG_PARTY", "CHAT_MSG_PARTY_LEADER"},
    RAID  = {"CHAT_MSG_RAID", "CHAT_MSG_RAID_LEADER"},
    GUILD = {"CHAT_MSG_GUILD"},
}

-- Forward declarations (assigned during UI creation / chat integration)
local MainFrame, MessageLog
local btnNewGame, btnChannel, btnEnterSelf, btnLastCall
local editRollValue, btnSelfRoll, btnRoll, btnStatsSession, btnStatsOverall
local btnResetGame, btnResetStats
local presetBtns = {}
local RegisterChatEvents, UnregisterChatEvents

-- ============================================================
-- Utilities
-- ============================================================

local function GetBaseName(name)
    if not name then return "Unknown" end
    return (strsplit("-", name))
end

local function Comma(number)
    if not number then return "0" end
    if type(number) ~= "number" then number = tonumber(number) end
    if not number then return "0" end
    local s = format("%.0f", math.floor(number + 0.5))
    local l, m, r = string.match(s, "^([^%d]*%d)(%d+)(.-)$")
    if l then
        return l .. string.reverse(string.gsub(string.reverse(m), "(%d%d%d)", "%1,")) .. r
    end
    return s
end

--- Gold display like vGambler: positives without "+", negatives as -12,345
local function FormatSignedGold(n)
    if not n then return "0" end
    if type(n) ~= "number" then n = tonumber(n) or 0 end
    if n < 0 then
        return "-" .. Comma(math.abs(n))
    end
    return Comma(n)
end

--- Sorted rows {name, value} from tbl.All (highest first).
local function BuildRankedRowsFromAll(allTbl)
    if not allTbl or not next(allTbl) then return nil end
    local temp = {}
    for k, v in pairs(allTbl) do
        tinsert(temp, {k, v})
    end
    table.sort(temp, function(a, b) return a[2] > b[2] end)
    return temp
end

local MAX_OVERALL_SNAPSHOTS = 60
local MAX_OVERALL_SNAPSHOTS_SHOW = 15

--- After each round, save a full ranked leaderboard snapshot (SavedVariables).
local function AppendOverallLogSnapshot()
    if not BBIGStats or not BBIGStats.All or not next(BBIGStats.All) then return end
    local rows = BuildRankedRowsFromAll(BBIGStats.All)
    if not rows then return end
    BBIGOverallLog = BBIGOverallLog or {}
    local lines = {}
    for i = 1, #rows do
        lines[i] = format("%d. %s: %s", i, rows[i][1], FormatSignedGold(rows[i][2]))
    end
    tinsert(BBIGOverallLog, 1, {t = GetServerTime(), lines = lines})
    while #BBIGOverallLog > MAX_OVERALL_SNAPSHOTS do
        table.remove(BBIGOverallLog)
    end
end

--- Party leader or raid leader (not assistants); solo = you. Used for New Game, stake, channel, chat roster.
local function IsLeader()
    if TestMode then return true end
    if not IsInGroup() then return true end
    return UnitIsGroupLeader("player")
end

--- Same authority as IsLeader when grouped; used for Last Call, Roll (lock), Reset Stats.
local function IsGroupLeader()
    if TestMode then return true end
    if not IsInGroup() then return true end
    return UnitIsGroupLeader("player")
end

--- Verify addon message sender is the actual party/raid leader (prevents spoofed LAST_CALL/CLOSE/etc.).
local function SenderIsGroupLeader(sender)
    if TestMode then return true end
    local senderBase = GetBaseName(sender)
    if not IsInGroup() then
        return senderBase == Me
    end
    if UnitIsGroupLeader("player") then
        return senderBase == Me
    end
    if IsInRaid() then
        for i = 1, GetNumGroupMembers() do
            local unit = "raid" .. i
            if UnitExists(unit) and UnitIsGroupLeader(unit) then
                return senderBase == GetBaseName(UnitName(unit))
            end
        end
    else
        for i = 1, GetNumSubgroupMembers() do
            local unit = "party" .. i
            if UnitExists(unit) and UnitIsGroupLeader(unit) then
                return senderBase == GetBaseName(UnitName(unit))
            end
        end
    end
    return false
end

--- True if chat sender is you or someone on your current party/raid roster (ignores guildies not in group).
local function IsSenderInOurGroup(sender)
    if not sender then return false end
    local want = GetBaseName(sender)
    if not IsInGroup() then
        return want == Me
    end
    if GetBaseName(UnitName("player")) == want then
        return true
    end
    if IsInRaid() then
        for i = 1, GetNumGroupMembers() do
            local unit = "raid" .. i
            if UnitExists(unit) then
                local n = UnitName(unit)
                if n and GetBaseName(n) == want then
                    return true
                end
            end
        end
    else
        for i = 1, GetNumSubgroupMembers() do
            local unit = "party" .. i
            if UnitExists(unit) then
                local n = UnitName(unit)
                if n and GetBaseName(n) == want then
                    return true
                end
            end
        end
    end
    return false
end

local function SendMsg(msg)
    if TestMode then return end
    local chan = Channel
    if chan == "RAID" and not IsInRaid() then
        chan = IsInGroup() and "PARTY" or "GUILD"
    elseif chan == "PARTY" and not IsInGroup() then
        chan = "GUILD"
    end
    C_ChatInfo.SendAddonMessage(PREFIX, msg, chan)
end

local function FindPlayer(name)
    for i = 1, #Players do
        if Players[i][1] == name then return i end
    end
end

-- ============================================================
-- Message Log Helpers
-- ============================================================

local function Log(text, r, g, b)
    if MessageLog then MessageLog:AddMessage(text, r or 1, g or 1, b or 1) end
end

local function LogSystem(t) Log(t, 1.0, 0.82, 0.0)  end -- gold
local function LogGood(t)   Log(t, 0.1, 1.0,  0.1)  end -- green
local function LogBad(t)    Log(t, 1.0, 0.1,  0.1)  end -- red
local function LogInfo(t)   Log(t, 0.82, 0.82, 0.82) end -- light grey
local function LogAlert(t)  Log(t, 1.0, 0.5,  0.0)  end -- orange

--- Print recent saved snapshots (overall log) to the addon MessageLog.
local function PostRecentOverallLog(maxShow)
    maxShow = maxShow or MAX_OVERALL_SNAPSHOTS_SHOW
    if not BBIGOverallLog or #BBIGOverallLog == 0 then
        LogInfo("  (Overall log is empty — finish some games to record snapshots.)")
        return
    end
    LogSystem("--- Overall log (saved after each game) ---")
    local n = math.min(maxShow, #BBIGOverallLog)
    for s = 1, n do
        local snap = BBIGOverallLog[s]
        if snap and snap.lines and #snap.lines > 0 then
            local when = snap.t and date("%m/%d %I:%M %p", snap.t) or "?"
            LogInfo("  @" .. when)
            for _, line in ipairs(snap.lines) do
                LogInfo("  " .. line)
            end
        end
    end
    if #BBIGOverallLog > n then
        LogInfo(format("  … %d older snapshot(s) in saved log.", #BBIGOverallLog - n))
    end
end

--- Colored text on RaidWarningFrame (boss-emphasis style). No SendChatMessage to group/guild.
local function RaidBBIG(msg, r, g, b)
    RaidNotice_AddMessage(RaidWarningFrame, msg, {r = r or 1, g = g or 0.85, b = b or 0.1})
end

-- ============================================================
-- State Helpers
-- ============================================================

local function ResetGameState()
    wipe(Rolls)
    wipe(Players)
    Locked = false
    GameActive = false
    StopMusic()
    if UnregisterChatEvents then UnregisterChatEvents() end
end

local function UpdateChannelLabel()
    if btnChannel then btnChannel:SetText(CHANNEL_LABELS[Channel] or Channel) end
end

local function UpdateButtons()
    if not MainFrame or not Me then return end
    local leader = IsLeader()
    local grpLeader = IsGroupLeader()

    if leader and not GameActive then btnNewGame:Enable()   else btnNewGame:Disable()   end
    if leader and not GameActive then btnChannel:Enable()   else btnChannel:Disable()   end
    if leader and not GameActive then editRollValue:Enable() else editRollValue:Disable() end

    if grpLeader and GameActive and not Locked then btnLastCall:Enable() else btnLastCall:Disable() end
    if grpLeader and GameActive then btnRoll:Enable()       else btnRoll:Disable()       end
    if leader and GameActive then btnResetGame:Enable()  else btnResetGame:Disable()  end

    if GameActive and not Locked then
        btnEnterSelf:Enable()
        if FindPlayer(Me) then
            btnEnterSelf:SetText("Leave Game")
        else
            btnEnterSelf:SetText("Enter Self")
        end
    else
        btnEnterSelf:Disable()
        btnEnterSelf:SetText("Enter Self")
    end

    local idx = FindPlayer(Me)
    if GameActive and Locked and idx and not Players[idx][2] then
        btnSelfRoll:Enable()
    else
        btnSelfRoll:Disable()
    end

    btnStatsSession:Enable()
    btnStatsOverall:Enable()
    if grpLeader then btnResetStats:Enable() else btnResetStats:Disable() end

    for _, btn in ipairs(presetBtns) do
        if leader and not GameActive then btn:Enable() else btn:Disable() end
    end
end

-- ============================================================
-- Stats
-- ============================================================

local function ApplyStatDelta(tbl, winName, loseName, diff)
    if not tbl.All then tbl.All = {} end
    if not tbl.Specific then tbl.Specific = {} end
    tbl.All[winName]  = (tbl.All[winName] or 0)  + diff
    tbl.All[loseName] = (tbl.All[loseName] or 0) - diff
    if not tbl.Specific[winName]  then tbl.Specific[winName]  = {} end
    if not tbl.Specific[loseName] then tbl.Specific[loseName] = {} end
    tbl.Specific[winName][loseName]  = (tbl.Specific[winName][loseName] or 0)  + diff
    tbl.Specific[loseName][winName]  = (tbl.Specific[loseName][winName] or 0) - diff
end

local function UpdateStats(winName, loseName, diff)
    if not BBIGStats then BBIGStats = {All = {}, Specific = {}} end
    if not BBIGStats.All then BBIGStats.All = {} end
    if not BBIGStats.Specific then BBIGStats.Specific = {} end
    ApplyStatDelta(BBIGStats, winName, loseName, diff)
    ApplyStatDelta(SessionStats, winName, loseName, diff)
    AppendOverallLogSnapshot()
end

--- @param tbl BBIGStats or SessionStats
--- @param player string|nil specific player name for pairwise view
--- @param aggregateTitle string header when listing all players (e.g. "This WoW session")
local function PostStatsFrom(tbl, player, aggregateTitle)
    if not tbl or (not tbl.All and not tbl.Specific) then
        LogSystem("No stats recorded yet.")
        return
    end

    if player then
        if tbl.Specific and tbl.Specific[player] and next(tbl.Specific[player]) then
            local temp = {}
            for k, v in pairs(tbl.Specific[player]) do tinsert(temp, {k, v}) end
            table.sort(temp, function(a, b) return a[2] > b[2] end)

            LogSystem(format("Specific stats for %s:", player))
            for i = 1, #temp do
                if temp[i][2] > 0 then
                    LogInfo(format("  %s owes %s %sg", temp[i][1], player, Comma(temp[i][2])))
                else
                    LogInfo(format("  %s owes %s %sg", player, temp[i][1], Comma(math.abs(temp[i][2]))))
                end
            end
        else
            LogSystem(format("No stats for %s.", player))
        end
    else
        if not tbl.All or not next(tbl.All) then
            if aggregateTitle then
                LogSystem((aggregateTitle or "Stats") .. ":")
                LogInfo("  (none)")
            else
                LogSystem("No stats recorded yet.")
            end
            return
        end
        local temp = {}
        for k, v in pairs(tbl.All) do tinsert(temp, {k, v}) end
        table.sort(temp, function(a, b) return a[2] > b[2] end)

        LogSystem((aggregateTitle or "BBIG Stats") .. ":")
        for i = 1, #temp do
            LogInfo(format("  %d. %s: %s", i, temp[i][1], FormatSignedGold(temp[i][2])))
        end
    end
end

local function PostStatsBoth()
    PostStatsFrom(SessionStats, nil, "This WoW session (since login)")
    PostStatsFrom(BBIGStats, nil, "All time (saved across sessions)")
end

-- ============================================================
-- Core Game Logic
-- ============================================================

local function SortRolls()
    table.sort(Rolls, function(a, b) return a[2] > b[2] end)

    local winner = Rolls[1]
    local loser  = Rolls[#Rolls]
    local diff   = winner[2] - loser[2]

    UpdateStats(winner[1], loser[1], diff)

    LogAlert(format("%s owes %s %sg!", loser[1], winner[1], Comma(diff)))

    local resultMsg = format("[BBIG] %s owes %s %sg!", loser[1], winner[1], Comma(diff))
    RaidBBIG(resultMsg, 1, 0.9, 0.25)

    if Me == winner[1] then
        LogGood(format("You WON! %s owes you %sg!", loser[1], Comma(diff)))
        RaidNotice_AddMessage(RaidWarningFrame,
            format("You WON! %s owes you %sg!", loser[1], Comma(diff)),
            {r = 0.1, g = 1.0, b = 0.1})
    elseif Me == loser[1] then
        PlayLoseStakeSound()
        LogBad(format("You LOST! You owe %s %sg!", winner[1], Comma(diff)))
        RaidNotice_AddMessage(RaidWarningFrame,
            format("You LOST! You owe %s %sg!", winner[1], Comma(diff)),
            {r = 1.0, g = 0.1, b = 0.1})
    end

    SendMsg(format("RESULT:%s:%d:%s:%d:%d", winner[1], winner[2], loser[1], loser[2], diff))

    ResetGameState()
    UpdateButtons()
end

local function StillNeedToRoll()
    local names = {}
    for i = 1, #Players do
        if not Players[i][2] then tinsert(names, Players[i][1]) end
    end
    return names
end

-- ============================================================
-- Test Mode Bot Simulation
-- ============================================================

local TEST_BOTS = {"OctoBro", "GoldGoblin", "LuckyFin"}

local function SimulateBotRolls()
    if not TestMode or not GameActive or not Locked then return end
    for i = 1, #Players do
        if Players[i][1] ~= Me and not Players[i][2] then
            Players[i][2] = true
            local roll = math.random(1, CurrentRollValue)
            tinsert(Rolls, {Players[i][1], roll})
            LogInfo(format("%s rolled %d (1-%s)", Players[i][1], roll, Comma(CurrentRollValue)))
        end
    end
    UpdateButtons()
    if #Rolls == #Players then
        SortRolls()
    end
end

-- ============================================================
-- Button Click Handlers
-- ============================================================

local function OnNewGameClick()
    if not IsLeader() or GameActive then return end

    local val = editRollValue:GetText():gsub(",", ""):gsub("%D", "")
    CurrentRollValue = tonumber(val) or 1000
    editRollValue:SetText(Comma(CurrentRollValue))

    ResetGameState()
    GameActive = true
    PlayMusic(MUSIC_FILE)

    RegisterChatEvents()
    SendMsg("NEW_GAME:" .. CurrentRollValue)
    LogSystem(format("New game started! Roll value: %sg.", Comma(CurrentRollValue)))
    local startMsg = format("[BBIG] New game by %s! Stake %sg — type 1 or Enter Self to join.", Me, Comma(CurrentRollValue))
    RaidBBIG(startMsg, 1, 0.88, 0.2)

    if TestMode then
        tinsert(Players, {Me})
        for _, bot in ipairs(TEST_BOTS) do
            tinsert(Players, {bot})
            LogInfo(format("%s has entered the game.", bot))
        end
        LogSystem("Test mode: you and 3 bots are in. Click 'Roll' to lock, then 'Self Roll'.")
    else
        LogSystem("Click 'Enter Self' to join, or type 1 in chat.")
    end

    UpdateButtons()
end

local function OnLastCallClick()
    if not IsGroupLeader() or not GameActive or Locked then return end
    PlaySound(5964, "SFX")
    SendMsg("LAST_CALL")
    LogAlert("Last call to enter!")
    RaidBBIG("[BBIG] LAST CALL — join the gamble now!", 1, 0.55, 0.1)
end

local function OnRollClick()
    if not IsGroupLeader() or not GameActive then return end

    if Locked then
        local still = StillNeedToRoll()
        if #still > 0 then
            local list = table.concat(still, ", ")
            SendMsg("STILL:" .. table.concat(still, ","))
            LogAlert("Still need to roll: " .. list)
        else
            LogInfo("All rolls are in. Processing...")
        end
    else
        if #Players < 2 then
            LogBad("Not enough players to start rolling!")
            return
        end
        Locked = true
        SendMsg("CLOSE:" .. CurrentRollValue)
        LogSystem(format("Game is closed! /roll %s now!", Comma(CurrentRollValue)))
        UpdateButtons()
        if TestMode then
            C_Timer.After(0.5, SimulateBotRolls)
        end
    end
end

local function OnEnterSelfClick()
    if not GameActive or Locked then return end
    if FindPlayer(Me) then
        SendMsg("QUIT")
    else
        SendMsg("JOIN")
    end
end

local function OnSelfRollClick()
    if not GameActive or not Locked then return end
    local idx = FindPlayer(Me)
    if not idx or Players[idx][2] then return end
    PlaySound(36629, "SFX")
    RandomRoll(1, CurrentRollValue)
end

local function OnResetGameClick()
    if not IsLeader() or not GameActive then return end
    ResetGameState()
    SendMsg("GAME_RESET")
    LogSystem("Game has been reset.")
    UpdateButtons()
end

local function OnSessionStatsClick()
    PostStatsFrom(SessionStats, nil, "This WoW session (since login)")
end

local function OnOverallStatsClick()
    PostStatsFrom(BBIGStats, nil, "All time (saved across sessions)")
    PostRecentOverallLog(MAX_OVERALL_SNAPSHOTS_SHOW)
end

local function OnResetStatsClick()
    if not IsGroupLeader() then return end
    BBIGStats = {All = {}, Specific = {}}
    SessionStats = {All = {}, Specific = {}}
    BBIGOverallLog = {}
    SendMsg("STATS_RESET")
    LogSystem("All stats have been reset (session + saved + overall log).")
end

local function OnChannelClick()
    if GameActive then return end
    ChannelIndex = ChannelIndex + 1
    if ChannelIndex > #CHANNELS then ChannelIndex = 1 end
    Channel = CHANNELS[ChannelIndex]
    UpdateChannelLabel()
end

-- ============================================================
-- Addon Message Handler
-- ============================================================

local function OnAddonMessage(prefix, msg, dist, sender)
    if prefix ~= PREFIX then return end
    local senderBase = GetBaseName(sender)
    local cmd, a1, a2, a3, a4, a5 = strsplit(":", msg)

    -- ---- Messages broadcast by the leader ----

    if cmd == "NEW_GAME" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        local rollVal = tonumber(a1)
        if rollVal then
            ResetGameState()
            CurrentRollValue = rollVal
            GameActive = true
            PlayMusic(MUSIC_FILE)
            Channel = dist
            for i, ch in ipairs(CHANNELS) do
                if ch == dist then ChannelIndex = i; break end
            end
            UpdateChannelLabel()
            editRollValue:SetText(Comma(CurrentRollValue))
            LogSystem(format("New game started by %s! Roll value: %sg.", senderBase, Comma(CurrentRollValue)))
            LogSystem("Click 'Enter Self' to join.")
            RaidBBIG(format("[BBIG] New game by %s! Stake %sg — type 1 or Enter Self to join.", senderBase, Comma(CurrentRollValue)), 1, 0.88, 0.2)
            UpdateButtons()
        end

    elseif cmd == "LAST_CALL" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        LogAlert("Last call to enter!")
        RaidBBIG("[BBIG] LAST CALL — join the gamble now!", 1, 0.55, 0.1)

    elseif cmd == "CLOSE" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        Locked = true
        local rollVal = tonumber(a1)
        if rollVal then CurrentRollValue = rollVal end
        LogSystem(format("Game is closed! /roll %s now!", Comma(CurrentRollValue)))
        UpdateButtons()

    elseif cmd == "ENTERED" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        local playerName = a1
        if playerName and not FindPlayer(playerName) then
            tinsert(Players, {playerName})
            LogInfo(format("%s has entered the game.", playerName))
            UpdateButtons()
        end

    elseif cmd == "LEFT" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        local playerName = a1
        local idx = FindPlayer(playerName)
        if idx then
            tremove(Players, idx)
            LogInfo(format("%s has left the game.", playerName))
            UpdateButtons()
        end

    elseif cmd == "STILL" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        if a1 and a1 ~= "" then
            LogAlert("Still need to roll: " .. a1:gsub(",", ", "))
        end

    elseif cmd == "RESULT" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        local winName  = a1
        local winRoll  = tonumber(a2)
        local loseName = a3
        local loseRoll = tonumber(a4)
        local diff     = tonumber(a5)
        if winName and loseName and diff then
            UpdateStats(winName, loseName, diff)
            LogAlert(format("%s owes %s %sg!", loseName, winName, Comma(diff)))

            RaidBBIG(format("[BBIG] %s owes %s %sg!", loseName, winName, Comma(diff)), 1, 0.9, 0.25)

            if Me == winName then
                LogGood(format("You WON! %s owes you %sg!", loseName, Comma(diff)))
                RaidNotice_AddMessage(RaidWarningFrame,
                    format("You WON! %s owes you %sg!", loseName, Comma(diff)),
                    {r = 0.1, g = 1.0, b = 0.1})
            elseif Me == loseName then
                PlayLoseStakeSound()
                LogBad(format("You LOST! You owe %s %sg!", winName, Comma(diff)))
                RaidNotice_AddMessage(RaidWarningFrame,
                    format("You LOST! You owe %s %sg!", winName, Comma(diff)),
                    {r = 1.0, g = 0.1, b = 0.1})
            end

            ResetGameState()
            UpdateButtons()
        end

    elseif cmd == "GAME_RESET" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        ResetGameState()
        LogSystem("Game has been reset by the leader.")
        UpdateButtons()

    elseif cmd == "STATS_RESET" then
        if senderBase == Me then return end
        if not SenderIsGroupLeader(sender) then return end
        BBIGStats = {All = {}, Specific = {}}
        SessionStats = {All = {}, Specific = {}}
        BBIGOverallLog = {}
        LogSystem("Stats have been reset by the leader.")

    -- ---- Messages from players → processed by leader only ----

    elseif cmd == "JOIN" then
        if not IsLeader() then return end
        if not GameActive or Locked then return end

        if not FindPlayer(senderBase) then
            tinsert(Players, {senderBase})
            SendMsg("ENTERED:" .. senderBase)
            LogInfo(format("%s has entered the game.", senderBase))
            UpdateButtons()
        end

    elseif cmd == "QUIT" then
        if not IsLeader() then return end
        local idx = FindPlayer(senderBase)
        if idx then
            tremove(Players, idx)
            SendMsg("LEFT:" .. senderBase)
            LogInfo(format("%s has left the game.", senderBase))
            UpdateButtons()
        end
    end
end

-- ============================================================
-- System Roll Detection (all clients track, leader resolves)
-- ============================================================

local function OnSystemMessage(msg)
    if not GameActive or not Locked then return end

    local name, roll, minR, maxR = string.match(msg, ROLL_PATTERN)
    if not name then return end
    if tonumber(minR) ~= 1 or tonumber(maxR) ~= CurrentRollValue then return end

    local baseName = GetBaseName(name)
    local idx = FindPlayer(baseName)
    if not idx or Players[idx][2] then return end

    Players[idx][2] = true
    tinsert(Rolls, {baseName, tonumber(roll)})
    LogInfo(format("%s rolled %d (%d-%s)", baseName, tonumber(roll), 1, Comma(CurrentRollValue)))

    if IsLeader() and #Rolls == #Players then
        SortRolls()
    else
        UpdateButtons()
    end
end

-- ============================================================
-- UI: Style Helpers
-- ============================================================

local FLAT = "Interface\\Buttons\\WHITE8x8"

local function MakeSep(parent, y)
    local t = parent:CreateTexture(nil, "ARTWORK")
    t:SetHeight(1)
    t:SetPoint("LEFT", 8, 0)
    t:SetPoint("RIGHT", -8, 0)
    t:SetPoint("TOP", 0, y)
    t:SetColorTexture(0.55, 0.10, 0.10, 0.35)
    return t
end

local function StyledBtn(parent, w, h, gold)
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(w, h)
    btn:RegisterForClicks("LeftButtonUp")
    btn:SetBackdrop({bgFile = FLAT, edgeFile = FLAT, edgeSize = 1})

    local nBg  = gold and {0.16, 0.14, 0.06, 0.95} or {0.13, 0.13, 0.18, 0.95}
    local nBdr = gold and {0.60, 0.50, 0.10, 0.80} or {0.40, 0.10, 0.10, 0.75}
    local hBg  = gold and {0.24, 0.20, 0.06, 0.95} or {0.24, 0.10, 0.10, 0.95}
    local hBdr = gold and {1.00, 0.82, 0.20, 1.00} or {0.90, 0.20, 0.15, 1.00}

    btn:SetBackdropColor(unpack(nBg))
    btn:SetBackdropBorderColor(unpack(nBdr))

    btn.label = btn:CreateFontString(nil, "OVERLAY")
    btn.label:SetFont(STANDARD_TEXT_FONT, 11, "OUTLINE")
    btn.label:SetPoint("CENTER")
    btn.label:SetTextColor(1, 0.85, 0.55)

    btn._on = true

    local function Sync(s)
        if s._on then
            s:SetBackdropColor(unpack(nBg))
            s:SetBackdropBorderColor(unpack(nBdr))
            s.label:SetTextColor(1, 0.85, 0.55)
        else
            s:SetBackdropColor(0.07, 0.07, 0.09, 0.70)
            s:SetBackdropBorderColor(0.14, 0.14, 0.17, 0.50)
            s.label:SetTextColor(0.32, 0.32, 0.36)
        end
    end

    function btn:SetText(t) self.label:SetText(t) end
    function btn:GetText()  return self.label:GetText() end
    function btn:Enable()   self._on = true;  self:EnableMouse(true);  Sync(self) end
    function btn:Disable()  self._on = false; self:EnableMouse(false); Sync(self) end

    btn:SetScript("OnEnter", function(s)
        if s._on then
            s:SetBackdropColor(unpack(hBg))
            s:SetBackdropBorderColor(unpack(hBdr))
        end
    end)
    btn:SetScript("OnLeave", function(s) Sync(s) end)

    Sync(btn)
    return btn
end

-- ============================================================
-- UI: Main Frame
-- ============================================================

MainFrame = CreateFrame("Frame", "BBIG_MainFrame", UIParent, "BackdropTemplate")
MainFrame:SetSize(400, 430)
MainFrame:SetPoint("CENTER")
MainFrame:SetMovable(true)
MainFrame:EnableMouse(true)
MainFrame:RegisterForDrag("LeftButton")
MainFrame:SetScript("OnDragStart", MainFrame.StartMoving)
MainFrame:SetScript("OnDragStop", MainFrame.StopMovingOrSizing)
MainFrame:SetBackdrop({bgFile = FLAT, edgeFile = FLAT, edgeSize = 2})
MainFrame:SetBackdropColor(0.07, 0.07, 0.11, 0.96)
MainFrame:SetBackdropBorderColor(0.50, 0.08, 0.08, 0.85)
MainFrame:SetFrameStrata("DIALOG")
MainFrame:Hide()

local f = MainFrame

local titleBar = CreateFrame("Frame", nil, f, "BackdropTemplate")
titleBar:SetHeight(36)
titleBar:SetPoint("TOPLEFT", 2, -2)
titleBar:SetPoint("TOPRIGHT", -2, -2)
titleBar:SetBackdrop({bgFile = FLAT})
titleBar:SetBackdropColor(0.10, 0.10, 0.16, 1)

local tIcon = titleBar:CreateTexture(nil, "ARTWORK")
tIcon:SetSize(24, 24)
tIcon:SetPoint("LEFT", 10, 0)
tIcon:SetTexture("Interface\\AddOns\\BrosBeforeIOsGambling\\icon")
tIcon:SetTexCoord(0.08, 0.92, 0.25, 0.95)

local tText = titleBar:CreateFontString(nil, "OVERLAY")
tText:SetFont(STANDARD_TEXT_FONT, 13, "OUTLINE")
tText:SetPoint("LEFT", tIcon, "RIGHT", 8, 0)
tText:SetText("|cffcc2222BROS BEFORE IOs|r  |cffffcc66GAMBLING|r")

local closeBtn = CreateFrame("Button", nil, titleBar)
closeBtn:SetSize(20, 20)
closeBtn:SetPoint("RIGHT", -4, 0)
closeBtn:SetNormalTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Up")
closeBtn:SetPushedTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Down")
closeBtn:SetHighlightTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Highlight", "ADD")
closeBtn:SetScript("OnClick", function() f:Hide() end)

MakeSep(f, -40)

-- ============================================================
-- UI: Scrolling Message Log
-- ============================================================

local logBg = CreateFrame("Frame", nil, f, "BackdropTemplate")
logBg:SetPoint("TOPLEFT", 8, -44)
logBg:SetPoint("TOPRIGHT", -8, -44)
logBg:SetHeight(170)
logBg:SetBackdrop({bgFile = FLAT, edgeFile = FLAT, edgeSize = 1})
logBg:SetBackdropColor(0.04, 0.04, 0.06, 1)
logBg:SetBackdropBorderColor(0.22, 0.22, 0.28, 0.50)

MessageLog = CreateFrame("ScrollingMessageFrame", "BBIG_MessageLog", logBg)
MessageLog:SetPoint("TOPLEFT", 8, -6)
MessageLog:SetPoint("BOTTOMRIGHT", -8, 6)
MessageLog:SetFontObject(GameFontNormalSmall)
MessageLog:SetJustifyH("LEFT")
MessageLog:SetFading(false)
MessageLog:SetMaxLines(500)
MessageLog:EnableMouseWheel(true)
MessageLog:SetScript("OnMouseWheel", function(self, delta)
    if delta > 0 then self:ScrollUp() else self:ScrollDown() end
end)

MakeSep(f, -218)

-- ============================================================
-- UI: Controls
-- ============================================================

local CY = -224
local CW = 126
local BW = 118
local BH = 24
local CH = 28

btnNewGame = StyledBtn(f, BW, BH)
btnNewGame:SetPoint("TOPLEFT", 10, CY)
btnNewGame:SetText("New Game")

btnChannel = StyledBtn(f, BW, BH)
btnChannel:SetPoint("TOPLEFT", 10 + CW, CY)
btnChannel:SetText(CHANNEL_LABELS[Channel])

btnEnterSelf = StyledBtn(f, BW, BH)
btnEnterSelf:SetPoint("TOPLEFT", 10 + CW * 2, CY)
btnEnterSelf:SetText("Enter Self")

btnLastCall = StyledBtn(f, BW, BH)
btnLastCall:SetPoint("TOPLEFT", 10, CY - CH)
btnLastCall:SetText("Last Call")

editRollValue = CreateFrame("EditBox", "BBIG_EditRollValue", f, "BackdropTemplate")
editRollValue:SetSize(BW - 6, BH - 2)
editRollValue:SetPoint("TOPLEFT", 10 + CW + 3, CY - CH - 1)
editRollValue:SetBackdrop({bgFile = FLAT, edgeFile = FLAT, edgeSize = 1})
editRollValue:SetBackdropColor(0.05, 0.05, 0.08, 1)
editRollValue:SetBackdropBorderColor(0.35, 0.35, 0.40, 0.60)
editRollValue:SetFont(STANDARD_TEXT_FONT, 12, "OUTLINE")
editRollValue:SetTextColor(1, 0.85, 0.55)
editRollValue:SetAutoFocus(false)
editRollValue:SetJustifyH("CENTER")
editRollValue:SetTextInsets(4, 4, 0, 0)
editRollValue:SetText(Comma(CurrentRollValue))
editRollValue:SetScript("OnEnterPressed", function(self)
    self:ClearFocus()
    local val = self:GetText():gsub(",", ""):gsub("%D", "")
    if val == "" then self:SetText(Comma(CurrentRollValue)); return end
    CurrentRollValue = tonumber(val) or CurrentRollValue
    self:SetText(Comma(CurrentRollValue))
end)
editRollValue:SetScript("OnEscapePressed", function(self)
    self:ClearFocus()
    self:SetText(Comma(CurrentRollValue))
end)

btnSelfRoll = StyledBtn(f, BW, BH)
btnSelfRoll:SetPoint("TOPLEFT", 10 + CW * 2, CY - CH)
btnSelfRoll:SetText("Self Roll")

btnRoll = StyledBtn(f, BW, BH)
btnRoll:SetPoint("TOPLEFT", 10, CY - CH * 2)
btnRoll:SetText("Roll")

btnStatsSession = StyledBtn(f, BW, BH)
btnStatsSession:SetPoint("TOPLEFT", 10 + CW, CY - CH * 2)
btnStatsSession:SetText("Session")

btnStatsOverall = StyledBtn(f, BW, BH)
btnStatsOverall:SetPoint("TOPLEFT", 10 + CW * 2, CY - CH * 2)
btnStatsOverall:SetText("All time")

btnResetGame = StyledBtn(f, BW, BH)
btnResetGame:SetPoint("TOPLEFT", 10, CY - CH * 3)
btnResetGame:SetText("Reset Game")

btnResetStats = StyledBtn(f, BW, BH)
btnResetStats:SetPoint("TOPLEFT", 10 + CW, CY - CH * 3)
btnResetStats:SetText("Reset Stats")

MakeSep(f, CY - CH * 4 - 2)

local qLabel = f:CreateFontString(nil, "OVERLAY")
qLabel:SetFont(STANDARD_TEXT_FONT, 9, "OUTLINE")
qLabel:SetPoint("TOPLEFT", 12, CY - CH * 4 - 8)
qLabel:SetTextColor(0.55, 0.55, 0.60)
qLabel:SetText("QUICK BET")

local PW = 90
local PG = 4
local PX = math.floor((400 - 4 * PW - 3 * PG) / 2)
local PY = CY - CH * 4 - 22
local PRESETS = {10000, 25000, 50000, 100000}
for i, amount in ipairs(PRESETS) do
    local btn = StyledBtn(f, PW, BH, true)
    btn:SetPoint("TOPLEFT", PX + (i - 1) * (PW + PG), PY)
    btn:SetText(Comma(amount))
    btn:SetScript("OnClick", function()
        if GameActive then return end
        if not IsLeader() then return end
        CurrentRollValue = amount
        editRollValue:SetText(Comma(amount))
    end)
    presetBtns[i] = btn
end

btnNewGame:SetScript("OnClick",    OnNewGameClick)
btnChannel:SetScript("OnClick",    OnChannelClick)
btnEnterSelf:SetScript("OnClick",  OnEnterSelfClick)
btnLastCall:SetScript("OnClick",   OnLastCallClick)
btnSelfRoll:SetScript("OnClick",   OnSelfRollClick)
btnRoll:SetScript("OnClick",       OnRollClick)
btnStatsSession:SetScript("OnClick", OnSessionStatsClick)
btnStatsOverall:SetScript("OnClick", OnOverallStatsClick)
btnResetGame:SetScript("OnClick",  OnResetGameClick)
btnResetStats:SetScript("OnClick", OnResetStatsClick)

-- ============================================================
-- UI: Twitch Plug
-- ============================================================

local twitchBar = CreateFrame("Frame", nil, f, "BackdropTemplate")
twitchBar:SetHeight(22)
twitchBar:SetPoint("BOTTOMLEFT", 2, 2)
twitchBar:SetPoint("BOTTOMRIGHT", -2, 2)
twitchBar:SetBackdrop({bgFile = FLAT, edgeFile = FLAT, edgeSize = 1})
twitchBar:SetBackdropColor(0.18, 0.10, 0.30, 0.85)
twitchBar:SetBackdropBorderColor(0.40, 0.25, 0.65, 0.70)
twitchBar:EnableMouse(true)

local twitchLabel = twitchBar:CreateFontString(nil, "OVERLAY")
twitchLabel:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE")
twitchLabel:SetPoint("CENTER")
twitchLabel:SetText("|cff9146fftwitch.tv/|r|cffffffffrespls|r")

twitchBar:SetScript("OnEnter", function(self)
    self:SetBackdropColor(0.25, 0.14, 0.42, 0.95)
    self:SetBackdropBorderColor(0.57, 0.35, 0.87, 0.90)
    GameTooltip:SetOwner(self, "ANCHOR_TOP")
    GameTooltip:AddLine("|cff9146fftwitch.tv/respls|r")
    GameTooltip:AddLine("Click to copy URL", 0.7, 0.7, 0.7)
    GameTooltip:Show()
end)
twitchBar:SetScript("OnLeave", function(self)
    self:SetBackdropColor(0.18, 0.10, 0.30, 0.85)
    self:SetBackdropBorderColor(0.40, 0.25, 0.65, 0.70)
    GameTooltip:Hide()
end)
twitchBar:SetScript("OnMouseUp", function()
    if not BBIG_URLPopup then
        local pop = CreateFrame("Frame", "BBIG_URLPopup", UIParent, "BackdropTemplate")
        pop:SetSize(280, 56)
        pop:SetPoint("CENTER")
        pop:SetBackdrop({bgFile = FLAT, edgeFile = FLAT, edgeSize = 1})
        pop:SetBackdropColor(0.10, 0.07, 0.16, 0.97)
        pop:SetBackdropBorderColor(0.57, 0.35, 0.87, 0.90)
        pop:SetFrameStrata("TOOLTIP")

        local pLbl = pop:CreateFontString(nil, "OVERLAY")
        pLbl:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE")
        pLbl:SetPoint("TOP", 0, -8)
        pLbl:SetText("|cff9146ffPress Ctrl+C to copy:|r")

        local pBox = CreateFrame("EditBox", nil, pop, "BackdropTemplate")
        pBox:SetSize(260, 22)
        pBox:SetPoint("BOTTOM", 0, 8)
        pBox:SetBackdrop({bgFile = FLAT, edgeFile = FLAT, edgeSize = 1})
        pBox:SetBackdropColor(0.05, 0.03, 0.08, 1)
        pBox:SetBackdropBorderColor(0.35, 0.25, 0.50, 0.60)
        pBox:SetFont(STANDARD_TEXT_FONT, 11, "OUTLINE")
        pBox:SetTextColor(1, 1, 1)
        pBox:SetAutoFocus(true)
        pBox:SetJustifyH("CENTER")
        pBox:SetTextInsets(4, 4, 0, 0)
        pBox:SetText("https://twitch.tv/respls")
        pBox:HighlightText()
        pBox:SetScript("OnEscapePressed", function() pop:Hide() end)
        pBox:SetScript("OnEnterPressed", function() pop:Hide() end)
        pop.box = pBox
    end
    BBIG_URLPopup:Show()
    BBIG_URLPopup.box:SetText("https://twitch.tv/respls")
    BBIG_URLPopup.box:HighlightText()
    BBIG_URLPopup.box:SetFocus()
end)

-- ============================================================
-- UI: Minimap Button
-- ============================================================

local minimapBtn = CreateFrame("Button", "BBIG_MinimapButton", Minimap)
minimapBtn:SetSize(32, 32)
minimapBtn:SetFrameStrata("MEDIUM")
minimapBtn:SetFrameLevel(8)
minimapBtn.angle = 220

local function UpdateMinimapPosition()
    local r = 104
    local x = math.cos(math.rad(minimapBtn.angle)) * r
    local y = math.sin(math.rad(minimapBtn.angle)) * r
    minimapBtn:ClearAllPoints()
    minimapBtn:SetPoint("CENTER", Minimap, "CENTER", x, y)
end
UpdateMinimapPosition()

local mmBorder = minimapBtn:CreateTexture(nil, "OVERLAY")
mmBorder:SetSize(56, 56)
mmBorder:SetPoint("TOPLEFT", 0, 0)
mmBorder:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")

local mmBg = minimapBtn:CreateTexture(nil, "BACKGROUND")
mmBg:SetSize(21, 21)
mmBg:SetPoint("TOPLEFT", 7, -5)
mmBg:SetTexture("Interface\\Minimap\\UI-Minimap-Background")

local mmIcon = minimapBtn:CreateTexture(nil, "ARTWORK")
mmIcon:SetSize(21, 21)
mmIcon:SetPoint("TOPLEFT", 7, -5)
mmIcon:SetTexture("Interface\\AddOns\\BrosBeforeIOsGambling\\icon")
mmIcon:SetTexCoord(0.08, 0.92, 0.25, 0.95)

minimapBtn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
minimapBtn:RegisterForDrag("LeftButton")

minimapBtn:SetScript("OnDragStart", function(self)
    self:LockHighlight()
    self:SetScript("OnUpdate", function(btn)
        local mx, my = Minimap:GetCenter()
        local px, py = GetCursorPosition()
        local s = Minimap:GetEffectiveScale()
        px, py = px / s, py / s
        btn.angle = math.deg(math.atan2(py - my, px - mx))
        UpdateMinimapPosition()
    end)
end)

minimapBtn:SetScript("OnDragStop", function(self)
    self:UnlockHighlight()
    self:SetScript("OnUpdate", nil)
    if BBIGSettings then BBIGSettings.minimapAngle = self.angle end
end)

minimapBtn:SetScript("OnClick", function()
    if f:IsShown() then f:Hide() else f:Show() end
end)

minimapBtn:SetScript("OnEnter", function(self)
    GameTooltip:SetOwner(self, "ANCHOR_LEFT")
    GameTooltip:AddLine("Bros Before IOs Gambling")
    GameTooltip:AddLine("Click to toggle window", 1, 1, 1)
    GameTooltip:AddLine("Drag to reposition", 0.7, 0.7, 0.7)
    GameTooltip:Show()
end)

minimapBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

-- ============================================================
-- Chat Integration (non-addon players: type 1 to enter, -1 to leave)
-- ============================================================

local chatFrame = CreateFrame("Frame")

RegisterChatEvents = function()
    chatFrame:UnregisterAllEvents()
    local events = CHAT_EVENTS[Channel]
    if events then
        for _, ev in ipairs(events) do
            chatFrame:RegisterEvent(ev)
        end
    end
end

UnregisterChatEvents = function()
    chatFrame:UnregisterAllEvents()
end

chatFrame:SetScript("OnEvent", function(self, event, message, sender)
    if not IsLeader() or not GameActive then return end
    if BBIGSettings and BBIGSettings.allowChatJoinLeave == false then return end
    if not IsSenderInOurGroup(sender) then return end
    local senderBase = GetBaseName(sender)

    if message == "1" and not Locked then
        if not FindPlayer(senderBase) then
            tinsert(Players, {senderBase})
            SendMsg("ENTERED:" .. senderBase)
            LogInfo(format("%s has entered the game.", senderBase))
            UpdateButtons()
        end
    elseif message == "-1" and not Locked then
        local idx = FindPlayer(senderBase)
        if idx then
            tremove(Players, idx)
            SendMsg("LEFT:" .. senderBase)
            LogInfo(format("%s has left the game.", senderBase))
            UpdateButtons()
        end
    end
end)

-- ============================================================
-- Options Panel (WoW Settings API)
-- ============================================================

local optionsFrame = CreateFrame("Frame", "BBIG_OptionsFrame")
optionsFrame.name = "Bros Before IOs Gambling"

local optTitle = optionsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
optTitle:SetPoint("TOPLEFT", 16, -16)
optTitle:SetText("Bros Before IOs Gambling")

local optDesc = optionsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
optDesc:SetPoint("TOPLEFT", optTitle, "BOTTOMLEFT", 0, -8)
optDesc:SetText("Type /bbig to toggle the gambling window, or click the minimap dice icon.")

local optOpenBtn = CreateFrame("Button", nil, optionsFrame, "UIPanelButtonTemplate")
optOpenBtn:SetSize(120, 22)
optOpenBtn:SetPoint("TOPLEFT", optDesc, "BOTTOMLEFT", 0, -16)
optOpenBtn:SetText("Open Window")
optOpenBtn:SetScript("OnClick", function()
    f:Show()
    SettingsPanel:Hide()
end)

local category = Settings.RegisterCanvasLayoutCategory(optionsFrame, optionsFrame.name)
Settings.RegisterAddOnCategory(category)

-- ============================================================
-- Slash Commands
-- ============================================================

SLASH_BBIG1 = "/bbig"
SlashCmdList["BBIG"] = function(input)
    local cmd, rest = strsplit(" ", input, 2)
    cmd = (cmd or ""):lower()

    if cmd == "" then
        if f:IsShown() then f:Hide() else f:Show() end
    elseif cmd == "show" then
        f:Show()
    elseif cmd == "hide" then
        f:Hide()
    elseif cmd == "help" then
        print("|cffff8000[BBIG]|r Commands:")
        print("  /bbig          - Toggle window")
        print("  /bbig leave    - Leave current game")
        print("  /bbig stats [session|overall|name]  - View stats")
        print("  /bbig resetstats")
        print("  /bbig chatjoin on|off  - Raid/party chat 1/-1 before lock (leader)")
        print("  /bbig overalllog      - Saved leaderboard snapshots (after each game)")
        print("  /bbig test     - Toggle test mode (solo with bots)")
    elseif cmd == "chatjoin" then
        local sub = (rest or ""):gsub("^%s+", ""):gsub("%s+$", ""):lower()
        if not IsGroupLeader() then
            print("|cffff8000[BBIG]|r Only the party or raid leader can change chat join settings.")
        else
            BBIGSettings = BBIGSettings or {}
            if sub == "off" or sub == "0" or sub == "false" or sub == "disable" then
                BBIGSettings.allowChatJoinLeave = false
                print("|cffff8000[BBIG]|r Chat |cffffffff1|r / |cffffffff-1|r is |cffff4444OFF|r. Use Enter Self or addon JOIN/QUIT only.")
            elseif sub == "on" or sub == "1" or sub == "true" or sub == "enable" or sub == "" then
                BBIGSettings.allowChatJoinLeave = true
                print("|cffff8000[BBIG]|r Chat |cffffffff1|r / |cffffffff-1|r is |cff44ff44ON|r (party/raid members in your group only).")
            else
                print("|cffff8000[BBIG]|r Usage: /bbig chatjoin on|off")
            end
        end
    elseif cmd == "test" then
        TestMode = not TestMode
        if TestMode then
            if GameActive then
                ResetGameState()
                UpdateButtons()
            end
            if not f:IsShown() then f:Show() end
            LogSystem("|cff00ccffTEST MODE ON|r - You are the leader with 3 bots.")
            LogSystem("Click 'New Game' to start, 'Roll' to lock, then 'Self Roll'.")
            print("|cffff8000[BBIG]|r |cff00ccffTest mode enabled.|r")
        else
            if GameActive then
                ResetGameState()
            end
            LogSystem("|cff00ccffTEST MODE OFF|r - Back to normal.")
            print("|cffff8000[BBIG]|r Test mode disabled.")
        end
        UpdateButtons()
    elseif cmd == "stats" then
        if not f:IsShown() then f:Show() end
        local r = rest and rest:match("^%s*(.-)%s*$") or ""
        if r == "" then
            PostStatsBoth()
        else
            local first, second = strsplit(" ", r, 2)
            first = (first or ""):lower()
            second = second and second:match("^%s*(.-)%s*$") or nil
            if first == "session" or first == "s" then
                if second and second ~= "" then
                    PostStatsFrom(SessionStats, second, nil)
                else
                    PostStatsFrom(SessionStats, nil, "This WoW session (since login)")
                end
            elseif first == "overall" or first == "all" or first == "o" or first == "a" then
                if second and second ~= "" then
                    PostStatsFrom(BBIGStats, second, nil)
                else
                    PostStatsFrom(BBIGStats, nil, "All time (saved across sessions)")
                    PostRecentOverallLog(MAX_OVERALL_SNAPSHOTS_SHOW)
                end
            else
                PostStatsFrom(BBIGStats, r, nil)
            end
        end
    elseif cmd == "leave" then
        if GameActive and not Locked and FindPlayer(Me) then
            SendMsg("QUIT")
        else
            print("|cffff8000[BBIG]|r Cannot leave right now.")
        end
    elseif cmd == "resetstats" then
        if not IsGroupLeader() then
            print("|cffff8000[BBIG]|r Only the party or raid leader can reset stats.")
        else
            BBIGStats = {All = {}, Specific = {}}
            SessionStats = {All = {}, Specific = {}}
            BBIGOverallLog = {}
            SendMsg("STATS_RESET")
            LogSystem("All stats have been reset (session + saved + overall log).")
        end
    elseif cmd == "overalllog" then
        if not f:IsShown() then f:Show() end
        PostRecentOverallLog(25)
    else
        print("|cffff8000[BBIG]|r Commands:")
        print("  /bbig          - Toggle window")
        print("  /bbig leave    - Leave current game")
        print("  /bbig stats [session|overall|name]  - View stats")
        print("  /bbig resetstats")
        print("  /bbig chatjoin on|off  - Raid/party chat 1/-1 before lock (leader)")
        print("  /bbig overalllog      - Saved leaderboard snapshots (after each game)")
        print("  /bbig test     - Toggle test mode (solo with bots)")
    end
end

-- ============================================================
-- Event Handler
-- ============================================================

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("CHAT_MSG_ADDON")
eventFrame:RegisterEvent("CHAT_MSG_SYSTEM")
eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")

eventFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        local loaded = ...
        if loaded == addonName then
            BBIGStats      = BBIGStats      or {All = {}, Specific = {}}
            BBIGSettings   = BBIGSettings   or {minimapAngle = 220}
            BBIGOverallLog = BBIGOverallLog or {}

            if BBIGSettings.minimapAngle then
                minimapBtn.angle = BBIGSettings.minimapAngle
                UpdateMinimapPosition()
            end
        end

    elseif event == "PLAYER_LOGIN" then
        Me = GetBaseName(UnitName("player"))
        SessionStats = {All = {}, Specific = {}}
        UpdateButtons()
        LogSystem("Bros Before IOs Gambling v2.1.7 loaded. Type /bbig to toggle window, /bbig help for commands.")

    elseif event == "CHAT_MSG_ADDON" then
        OnAddonMessage(...)

    elseif event == "CHAT_MSG_SYSTEM" then
        OnSystemMessage(...)

    elseif event == "GROUP_ROSTER_UPDATE" then
        UpdateButtons()
    end
end)
