Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Documentation for this module may be created at Module:BGMIHub/doc

local p     = {}
local cargo = mw.ext.cargo
local html  = mw.html

-- ── Helpers ──────────────────────────────────────────────────────

local function esc(s)
    if not s then return "" end
    return s:gsub("\\","\\\\"):gsub("'","\\'")
end
local function clean(s)
    if not s then return nil end
    local c = s:gsub("[\r\n]",""):gsub("^%s*(.-)%s*$","%1")
    return c ~= "" and c or nil
end
local function fmtCurrency(n)
    if not n or n == 0 then return "—" end
    local num = math.floor(tonumber(n) or 0)
    local s   = tostring(num)
    if #s <= 3 then return "₹ " .. s end
    local result = s:sub(-3)
    local rem    = s:sub(1,-4)
    while #rem > 2 do result = rem:sub(-2)..","..result; rem = rem:sub(1,-3) end
    return "₹ " .. rem .. "," .. result
end
local function fmtNumber(n)
    -- Simple comma formatting for integers
    local num = math.floor(tonumber(n) or 0)
    local s   = tostring(num)
    local result = ""
    local len = #s
    for i = 1, len do
        if i > 1 and (len - i + 1) % 3 == 0 then result = result .. "," end
        result = result .. s:sub(i,i)
    end
    return result
end
local function getTierClass(tier)
    if not tier then return "tier-def" end
    local t = tier:lower()
    if t:find("s") and t:find("tier") then return "tier-s" end
    if t:find("a") and t:find("tier") then return "tier-a" end
    if t:find("b") and t:find("tier") then return "tier-b" end
    if t:find("c") and t:find("tier") then return "tier-c" end
    return "tier-def"
end

local GAME = "BGMI"

-- ── Section header builder ────────────────────────────────────────
local function sectionHeader(parent, icon, title, subtitle)
    local hdr = parent:tag('div'):addClass('hub-section-header')
    local hl  = hdr:tag('div'):addClass('hub-section-hl')
    hl:tag('h2'):addClass('hub-section-title'):wikitext(title)
    if subtitle then
        hdr:tag('div'):addClass('hub-section-sub'):wikitext(subtitle)
    end
end

-- ── Main ─────────────────────────────────────────────────────────

function p.main(frame)

    -- ════════════════════════════════════════════════════════════
    -- DATA FETCHING
    -- ════════════════════════════════════════════════════════════

    -- 1. Hero stats
    local totalTeamsRows = cargo.query("Teams","COUNT(name)=n",
        {where="game='"..GAME.."' AND status='Active'", limit=1})
    local totalTeams = (totalTeamsRows and #totalTeamsRows>0)
        and (tonumber(totalTeamsRows[1].n) or 0) or 0

    local totalPlayersRows = cargo.query("Players","COUNT(id)=n",
        {where="game='"..GAME.."' AND status='Active'", limit=1})
    local totalPlayers = (totalPlayersRows and #totalPlayersRows>0)
        and (tonumber(totalPlayersRows[1].n) or 0) or 0

    local totalTournsRows = cargo.query("Tournaments","COUNT(name)=n",
        {where="game='"..GAME.."'", limit=1})
    local totalTourns = (totalTournsRows and #totalTournsRows>0)
        and (tonumber(totalTournsRows[1].n) or 0) or 0

    local totalPrizeRows = cargo.query("PrizeMoney","SUM(prize)=total",
        {where="player='' OR player IS NULL", limit=1})
    local totalPrize = (totalPrizeRows and #totalPrizeRows>0)
        and (tonumber(totalPrizeRows[1].total) or 0) or 0

    -- 2. Tournaments — ongoing/upcoming (end_date >= today or no end_date)
    local today = os.date("%Y-%m-%d")
    local upcomingRows = cargo.query("Tournaments",
        "name,tier,start_date,end_date,prize_pool,image,image_dark,type,location",
        { where  = "game='"..GAME.."' AND (end_date>='"..today.."' OR end_date IS NULL OR end_date='')",
          orderBy= "start_date ASC",
          limit  = 6 })
    upcomingRows = upcomingRows or {}

    -- 3. Tournaments — recent results (completed, end_date < today)
    local recentRows = cargo.query("Tournaments",
        "name,tier,end_date,prize_pool,image,image_dark",
        { where  = "game='"..GAME.."' AND end_date<'"..today.."' AND end_date!=''",
          orderBy= "end_date DESC",
          limit  = 8 })
    recentRows = recentRows or {}

    -- Fetch winner + runner_up from PrizeMoney for each recent tournament
    local recentTournNames = {}
    for _,t in ipairs(recentRows) do
        table.insert(recentTournNames, "'"..esc(t.name).."'")
    end
    local podiumMap = {}  -- tournament -> { winner, runner_up }
    if #recentTournNames > 0 then
        local podiumRows = cargo.query("PrizeMoney", "tournament,team,placement",
            { where   = "tournament IN ("..table.concat(recentTournNames,",")..")"
                        .." AND (player='' OR player IS NULL)"
                        .." AND (placement='1' OR placement='2')",
              orderBy = "tournament, placement ASC",
              limit   = 50 })
        if podiumRows then
            for _,r in ipairs(podiumRows) do
                if not podiumMap[r.tournament] then
                    podiumMap[r.tournament] = {}
                end
                if r.placement == "1" then
                    podiumMap[r.tournament].winner = r.team
                elseif r.placement == "2" then
                    podiumMap[r.tournament].runner_up = r.team
                end
            end
        end
    end

    -- 4. Active teams with Krafton rank
    local teamRows = cargo.query("Teams",
        "name,display_name,short_code,image,image_dark,country,status",
        { where  = "game='"..GAME.."' AND status='Active'",
          orderBy= "name ASC",
          limit  = 50 })
    teamRows = teamRows or {}

    -- Fetch Krafton ranks for teams
    local teamRankMap = {}
    local allTeamRankRows = cargo.query("Krafton_Rankings","name,rank",
        {where="type='Team'", limit=100})
    if allTeamRankRows then
        for _,r in ipairs(allTeamRankRows) do
            teamRankMap[r.name] = tonumber(r.rank) or 999
        end
    end
    -- Sort teams by Krafton rank
    table.sort(teamRows, function(a,b)
        local ra = teamRankMap[a.name] or 999
        local rb = teamRankMap[b.name] or 999
        return ra < rb
    end)

    -- 5. Player Krafton rankings (top 10)
    local playerRankRows = cargo.query("Krafton_Rankings","name,rank",
        {where="type='Player'", orderBy="rank ASC", limit=10})
    playerRankRows = playerRankRows or {}

    -- Fetch player details for ranked players
    local rankedPlayerNames = {}
    for _,r in ipairs(playerRankRows) do
        table.insert(rankedPlayerNames,"'"..esc(r.name).."'")
    end
    local playerDetailMap = {}
    if #rankedPlayerNames>0 then
        local pdRows = cargo.query("Players","id,real_name,current_team,image,_pageName",
            {where="id IN ("..table.concat(rankedPlayerNames,",")..")", limit=20})
        if pdRows then
            for _,d in ipairs(pdRows) do
                playerDetailMap[d.id]={
                    real_name=d.real_name, team=d.current_team,
                    image=d.image, link=d._pageName
                }
            end
        end
    end

    -- 6. Team earnings leaderboard (top 8)
    local teamEarnRows = cargo.query("PrizeMoney","team,SUM(prize)=total",
        { where   = "player='' OR player IS NULL",
          groupBy = "team",
          orderBy = "total DESC",
          limit   = 8 })
    teamEarnRows = teamEarnRows or {}

    -- 7. Player earnings leaderboard (top 8)
    local playerEarnRows = cargo.query("PrizeMoney","player,SUM(prize)=total",
        { where   = "player!='' AND player IS NOT NULL",
          groupBy = "player",
          orderBy = "total DESC",
          limit   = 8 })
    playerEarnRows = playerEarnRows or {}

    -- Fetch player IDs for earnings leaderboard
    local earnPlayerNames = {}
    for _,r in ipairs(playerEarnRows) do
        if r.player and r.player~="" then
            table.insert(earnPlayerNames,"'"..esc(r.player).."'")
        end
    end
    local earnPlayerMap = {}
    if #earnPlayerNames>0 then
        local epRows = cargo.query("Players","id,real_name,current_team,_pageName",
            {where="_pageName IN ("..table.concat(earnPlayerNames,",")..")", limit=20})
        if epRows then
            for _,d in ipairs(epRows) do
                earnPlayerMap[d._pageName]={
                    id=d.id, real_name=d.real_name,
                    team=d.current_team, link=d._pageName
                }
            end
        end
    end

    -- ════════════════════════════════════════════════════════════
    -- RENDER
    -- ════════════════════════════════════════════════════════════

    local root = html.create('div'):addClass('hub-root')

    -- ════ 1. HERO ════════════════════════════════════════════════
    local hero = root:tag('div'):addClass('hub-hero')
    local heroInner = hero:tag('div'):addClass('hub-hero-inner')

    -- Left: title + description
    local heroLeft = heroInner:tag('div'):addClass('hub-hero-left')
    heroLeft:tag('div'):addClass('hub-hero-game-tag'):wikitext('ESPORTS WIKI')
    heroLeft:tag('h1'):addClass('hub-hero-title'):wikitext('BGMI')
    heroLeft:tag('div'):addClass('hub-hero-full'):wikitext('Battlegrounds Mobile India')
    heroLeft:tag('div'):addClass('hub-hero-desc')
        :wikitext('The complete resource for competitive BGMI — teams, players, tournaments and prize money all in one place.')

    -- Right: live stat cards
    local stats = heroInner:tag('div'):addClass('hub-hero-stats')
    local function statCard(val, label, icon)
        local card = stats:tag('div'):addClass('hub-stat-card')
        card:tag('div'):addClass('hub-stat-val'):wikitext(val)
        card:tag('div'):addClass('hub-stat-label'):wikitext(label)
    end
    statCard(tostring(totalTourns),  'Tournaments',    '')
    statCard(tostring(totalTeams),   'Active Teams',   '')
    statCard(tostring(totalPlayers), 'Active Players', '')
    statCard(fmtCurrency(totalPrize),'Prize Distributed','')

    -- ════ 2. TOURNAMENTS ═════════════════════════════════════════
    local tourns = root:tag('div'):addClass('hub-section')
    sectionHeader(tourns, '', 'Tournaments', 'Ongoing, upcoming and recent results')

    -- Upcoming / Ongoing
    if #upcomingRows>0 then
        tourns:tag('div'):addClass('hub-sub-title'):wikitext('Ongoing & Upcoming')
        local tGrid = tourns:tag('div'):addClass('hub-tourn-grid')
        for _,t in ipairs(upcomingRows) do
            local card = tGrid:tag('div'):addClass('hub-tourn-card')

            -- Tier ribbon
            local tier = t.tier or ""
            if tier~="" then
                card:tag('div'):addClass('hub-tourn-tier')
                    :tag('span'):addClass('tier-badge '..getTierClass(tier)):wikitext(tier)
            end

            -- Logo
            local lightL = (t.image and t.image~="") and t.image or "Tournament_Placeholder.png"
            local darkL  = (t.image_dark and t.image_dark~="") and t.image_dark or lightL
            local logoW  = card:tag('div'):addClass('hub-tourn-logo')
            logoW:wikitext("[[File:"..lightL.."|60px|link=|class=logo-lightmode]]")
            logoW:wikitext("[[File:"..darkL .."|60px|link=|class=logo-darkmode]]")

            -- Info
            local info = card:tag('div'):addClass('hub-tourn-info')
            info:tag('div'):addClass('hub-tourn-name')
                :wikitext("[["..t.name.."|"..t.name.."]]")

            -- Dates
            local dateStr = ""
            if t.start_date and t.start_date~="" then
                dateStr = t.start_date
                if t.end_date and t.end_date~="" then
                    dateStr = dateStr.." → "..t.end_date
                end
            end
            if dateStr~="" then
                info:tag('div'):addClass('hub-tourn-dates'):wikitext(dateStr)
            end

            -- Prize + location row
            local meta = info:tag('div'):addClass('hub-tourn-meta')
            if t.prize_pool and tonumber(t.prize_pool) and tonumber(t.prize_pool)>0 then
                meta:tag('span'):addClass('hub-tourn-prize')
                    :wikitext('💰 '..fmtCurrency(tonumber(t.prize_pool)))
            end
            if t.location and t.location~="" then
                meta:tag('span'):addClass('hub-tourn-loc'):wikitext('📍 '..t.location)
            end

            -- Live badge if ongoing
            if t.start_date and t.start_date<=today
               and (not t.end_date or t.end_date=="" or t.end_date>=today) then
                card:tag('div'):addClass('hub-live-badge'):wikitext('LIVE')
            end
        end
    end

    -- Recent Results
    if #recentRows>0 then
        tourns:tag('div'):addClass('hub-sub-title hub-sub-title-gap'):wikitext('Recent Results')
        local tbl = tourns:tag('table'):addClass('hub-results-table')
        local hdr = tbl:tag('tr')
        hdr:tag('th'):wikitext('Date')
        hdr:tag('th'):wikitext('Tier')
        hdr:tag('th'):wikitext('Tournament')
        hdr:tag('th'):wikitext('Winner')
        hdr:tag('th'):wikitext('Runner-up')
        hdr:tag('th'):css('text-align','right'):wikitext('Prize Pool')

        for _,t in ipairs(recentRows) do
            local tr = tbl:tag('tr')
            tr:tag('td'):addClass('ac-date'):wikitext(t.end_date or "—")
            local tierTd = tr:tag('td'):addClass('ac-tier')
            if t.tier and t.tier~="" then
                tierTd:tag('span'):addClass('tier-badge '..getTierClass(t.tier)):wikitext(t.tier)
            else tierTd:wikitext("—") end
            tr:tag('td'):css('font-weight','600'):wikitext("[["..t.name.."]]")
            local podium = podiumMap[t.name] or {}
            local w  = podium.winner    and podium.winner~=""    and podium.winner    or nil
            local ru = podium.runner_up and podium.runner_up~="" and podium.runner_up or nil
            tr:tag('td'):css('font-weight','600')
                :wikitext(w  and "[[BGMI/Teams/"..w .."|"..w .."]]" or "—")
            tr:tag('td')
                :wikitext(ru and "[[BGMI/Teams/"..ru.."|"..ru.."]]" or "—")
            tr:tag('td'):addClass('ac-prize')
                :wikitext(tonumber(t.prize_pool) and tonumber(t.prize_pool)>0
                    and fmtCurrency(tonumber(t.prize_pool)) or "—")
        end
    end

    -- ════ 3. TEAMS ═══════════════════════════════════════════════
    local teamsSection = root:tag('div'):addClass('hub-section')
    sectionHeader(teamsSection, '', 'Teams',
        'Active BGMI organisations, sorted by Krafton rank')

    local teamGrid = teamsSection:tag('div'):addClass('hub-team-grid')
    for _,t in ipairs(teamRows) do
        local card = teamGrid:tag('div'):addClass('hub-team-card')
        local rank = teamRankMap[t.name]

        -- Rank badge
        if rank and rank<999 then
            card:tag('div'):addClass('hub-team-rank'):wikitext('#'..rank)
        end

        -- Logo
        local lightL=(t.image and t.image~="") and t.image or "Shield_team.png"
        local darkL =(t.image_dark and t.image_dark~="") and t.image_dark or lightL
        local logoW = card:tag('div'):addClass('hub-team-logo')
        logoW:wikitext("[[File:"..lightL.."|48px|link="..t.name.."|class=logo-lightmode]]")
        logoW:wikitext("[[File:"..darkL .."|48px|link="..t.name.."|class=logo-darkmode]]")

        -- Name
        local dispName = (t.display_name and t.display_name~="")
            and t.display_name or t.name
        card:tag('div'):addClass('hub-team-name')
            :wikitext("[["..t.name.."|"..dispName.."]]")
        if t.short_code and t.short_code~="" then
            card:tag('div'):addClass('hub-team-tag'):wikitext(t.short_code)
        end
    end

    -- ════ 4. RANKINGS ════════════════════════════════════════════
    local rankSection = root:tag('div'):addClass('hub-section hub-rankings')
    sectionHeader(rankSection, '', 'Krafton Rankings', 'Official BGMI rankings')

    local rankCols = rankSection:tag('div'):addClass('hub-rank-cols')

    -- Player rankings
    local pRankCol = rankCols:tag('div'):addClass('hub-rank-col')
    pRankCol:tag('div'):addClass('hub-rank-col-title'):wikitext('Top Players')
    local pTbl = pRankCol:tag('table'):addClass('hub-rank-table')
    local ph = pTbl:tag('tr')
    ph:tag('th'):wikitext('Rank')
    ph:tag('th'):wikitext('Player')
    ph:tag('th'):wikitext('Team')

    for _,r in ipairs(playerRankRows) do
        local d = playerDetailMap[r.name] or {}
        local tr = pTbl:tag('tr')
        -- Rank with medal for top 3
        local rankTd = tr:tag('td'):addClass('hub-rank-num')
        local rnum = tonumber(r.rank) or 99
        if rnum == 1 then rankTd:wikitext('🥇')
        elseif rnum == 2 then rankTd:wikitext('🥈')
        elseif rnum == 3 then rankTd:wikitext('🥉')
        else rankTd:tag('span'):addClass('hub-rank-badge'):wikitext('#'..r.rank) end

        local link = d.link or r.name
        tr:tag('td'):css('font-weight','700')
            :wikitext("[["..link.."|"..r.name.."]]")
        tr:tag('td'):addClass('hub-rank-team')
            :wikitext(d.team and d.team~="" and "[["..d.team.."]]" or "—")
    end

    -- Team rankings
    local tRankCol = rankCols:tag('div'):addClass('hub-rank-col')
    tRankCol:tag('div'):addClass('hub-rank-col-title'):wikitext('Top Teams')
    local tTbl = tRankCol:tag('table'):addClass('hub-rank-table')
    local th2 = tTbl:tag('tr')
    th2:tag('th'):wikitext('Rank')
    th2:tag('th'):wikitext('Team')

    -- Show top 10 teams by rank
    local topTeams = {}
    for _,t in ipairs(teamRows) do
        if teamRankMap[t.name] and teamRankMap[t.name]<999 then
            table.insert(topTeams,t)
        end
        if #topTeams>=10 then break end
    end
    for _,t in ipairs(topTeams) do
        local rank = teamRankMap[t.name]
        local tr = tTbl:tag('tr')
        local rankTd = tr:tag('td'):addClass('hub-rank-num')
        if rank == 1 then rankTd:wikitext('🥇')
        elseif rank == 2 then rankTd:wikitext('🥈')
        elseif rank == 3 then rankTd:wikitext('🥉')
        else rankTd:tag('span'):addClass('hub-rank-badge'):wikitext('#'..rank) end
        local dispName=(t.display_name and t.display_name~="") and t.display_name or t.name
        tr:tag('td'):css('font-weight','700')
            :wikitext("[["..t.name.."|"..dispName.."]]")
    end

    -- ════ 5. PRIZE LEADERBOARD ═══════════════════════════════════
    local prizeSection = root:tag('div'):addClass('hub-section')
    sectionHeader(prizeSection, '', 'Prize Money Leaderboard',
        'All-time earnings from recorded tournaments')

    local prizeCols = prizeSection:tag('div'):addClass('hub-rank-cols')

    -- Team earnings
    local teCol = prizeCols:tag('div'):addClass('hub-rank-col')
    teCol:tag('div'):addClass('hub-rank-col-title'):wikitext('Top Teams')
    local teTbl = teCol:tag('table'):addClass('hub-rank-table')
    local teh = teTbl:tag('tr')
    teh:tag('th'):wikitext('#')
    teh:tag('th'):wikitext('Team')
    teh:tag('th'):css('text-align','right'):wikitext('Earnings')

    for i,r in ipairs(teamEarnRows) do
        local tr = teTbl:tag('tr')
        tr:tag('td'):addClass('hub-rank-num'):wikitext(tostring(i))
        tr:tag('td'):css('font-weight','700')
            :wikitext(r.team and r.team~="" and "[["..r.team.."]]" or "—")
        tr:tag('td'):addClass('ac-prize'):wikitext(fmtCurrency(tonumber(r.total) or 0))
    end

    -- Player earnings
    local peCol = prizeCols:tag('div'):addClass('hub-rank-col')
    peCol:tag('div'):addClass('hub-rank-col-title'):wikitext('Top Players')
    local peTbl = peCol:tag('table'):addClass('hub-rank-table')
    local peh = peTbl:tag('tr')
    peh:tag('th'):wikitext('#')
    peh:tag('th'):wikitext('Player')
    peh:tag('th'):css('text-align','right'):wikitext('Earnings')

    for i,r in ipairs(playerEarnRows) do
        local d = earnPlayerMap[r.player] or {}
        local dispId = d.id or (r.player or ""):gsub("^.*/","")
        local link   = d.link or r.player or ""
        local tr = peTbl:tag('tr')
        tr:tag('td'):addClass('hub-rank-num'):wikitext(tostring(i))
        tr:tag('td'):css('font-weight','700')
            :wikitext(link~="" and "[["..link.."|"..dispId.."]]" or dispId)
        tr:tag('td'):addClass('ac-prize'):wikitext(fmtCurrency(tonumber(r.total) or 0))
    end

    return tostring(root)
end

return p