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

Module:PlayerDashboard: Difference between revisions

From eSportsAmaze
No edit summary
No edit summary
Line 1: Line 1:
-- ================================================================
-- ================================================================
-- Module:PlayerDashboard v3
-- Module:PlayerDashboard v3
-- Full rewrite with all fixes:
--  • Circular photo, vertically centered hero
--  • Current team: logo (light/dark) + display_name
--  • Team history: logos (light/dark)
--  • Individual awards table (from PrizeMoney where player= set)
--  • Team results table:
--      - Place from new Tournament_Results cargo table
--      - Prize/award from PrizeMoney (team rows)
--      - Shows ALL tournaments player appeared in
--      - Place/Award column shows chip OR award badge
--  • Flag emojis for nationality
--  • Dark mode link + badge overrides via CSS variables
--  • Mobile friendly
-- ================================================================
-- ================================================================


Line 41: Line 28:
     if not d or d == "" then return "?" end
     if not d or d == "" then return "?" end
     return lang:formatDate("M y", d)
     return lang:formatDate("M y", d)
end
-- Matches getTierClass used in Module:Team and Module:Tournament
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
end


Line 80: Line 78:
end
end


-- Single image wikitext (player photo etc.)
-- Team logo: outputs both light + dark versions, CSS switches them
local function logoWikitext(imageFile, size, classes)
-- Uses your existing .logo-lightmode / .logo-darkmode classes
    if not imageFile or imageFile == "" then return nil end
    size    = size    or "26px"
    classes = classes or "pd-team-logo-img"
    return "[[File:" .. imageFile .. "|" .. size .. "|link=|class=" .. classes .. "]]"
end
 
-- Team logo with light/dark switching
-- Uses your existing logo-lightmode / logo-darkmode CSS classes
local function teamLogoWikitext(ti, size)
local function teamLogoWikitext(ti, size)
     if not ti then return nil end
     if not ti then return nil end
Line 98: Line 88:
     return "[[File:"..light.."|"..size.."|link=|class=pd-team-logo-img logo-lightmode]]"
     return "[[File:"..light.."|"..size.."|link=|class=pd-team-logo-img logo-lightmode]]"
         .. "[[File:"..dark .."|"..size.."|link=|class=pd-team-logo-img logo-darkmode]]"
         .. "[[File:"..dark .."|"..size.."|link=|class=pd-team-logo-img logo-darkmode]]"
end
-- Place badge — matches achievements table style exactly
-- (uses .place-badge, .place-1, .place-2, .place-3, .place-def)
local function renderPlaceBadge(td, placeNum)
    local badgeClass = "place-def"
    local placeText  = tostring(placeNum) .. "th"
    if placeNum == 1 then badgeClass = "place-1"; placeText = "1st"
    elseif placeNum == 2 then badgeClass = "place-2"; placeText = "2nd"
    elseif placeNum == 3 then badgeClass = "place-3"; placeText = "3rd"
    end
    td:tag('span'):addClass('place-badge ' .. badgeClass):wikitext(placeText)
end
end


Line 132: Line 134:
             sqlPage,sqlName, sqlPage,sqlName, sqlPage,sqlName),
             sqlPage,sqlName, sqlPage,sqlName, sqlPage,sqlName),
         limit = 500 })
         limit = 500 })
    participation = participation or {}
    -- Quick lookup: tournament -> team the player was on (for individual awards)
    local tournTeamMap = {}
    for _, r in ipairs(participation) do
        if r.tournament and r.team and not tournTeamMap[r.tournament] then
            tournTeamMap[r.tournament] = r.team
        end
    end


     -- ── 4. Earnings ───────────────────────────────────────────────
     -- ── 4. Earnings ───────────────────────────────────────────────
     local indEarnings, teamEarnings = 0, 0
     local indEarnings, teamEarnings = 0, 0
     local eRows = cargo.query("PrizeMoney", "SUM(prize)=total", {
     local eRows = cargo.query("PrizeMoney", "SUM(prize)=total", {
         where = string.format(
         where = string.format(
             "(player='%s' OR player='%s')", sqlPage, sqlName) })
             "(player='%s' OR player='%s') AND player != ''", sqlPage, sqlName) })
     if eRows and #eRows > 0 then
     if eRows and #eRows > 0 then
         indEarnings = tonumber(eRows[1].total) or 0
         indEarnings = tonumber(eRows[1].total) or 0
     end
     end


     if participation and #participation > 0 then
     if #participation > 0 then
         local conds = {}
         local conds = {}
         for _, r in ipairs(participation) do
         for _, r in ipairs(participation) do
Line 165: Line 177:
     local teamHist = cargo.query("Player_Former_Teams",
     local teamHist = cargo.query("Player_Former_Teams",
         "team,join_date,leave_date",
         "team,join_date,leave_date",
         { where   = "player_id='" .. sqlPage .. "'",
         { where = "player_id='" .. sqlPage .. "'",
           orderBy = "join_date DESC", limit = 20 })
           orderBy = "join_date DESC", limit = 20 })
     if #teamHist == 0 then
     if not teamHist or #teamHist == 0 then
         teamHist = cargo.query("Player_Former_Teams",
         teamHist = cargo.query("Player_Former_Teams",
             "team,join_date,leave_date",
             "team,join_date,leave_date",
             { where   = "player_id='" .. sqlName .. "'",
             { where = "player_id='" .. sqlName .. "'",
               orderBy = "join_date DESC", limit = 20 })
               orderBy = "join_date DESC", limit = 20 })
     end
     end
    teamHist = teamHist or {}


     -- ── 6a. Individual awards ─────────────────────────────────────
     -- ── 6a. Individual awards ─────────────────────────────────────
    -- PrizeMoney rows where player= is set (individual awards)
     local indAwards = cargo.query("PrizeMoney",
     local indAwards = cargo.query("PrizeMoney",
         "tournament,award,prize",
         "tournament,award,prize",
         { where   = string.format(
         { where = string.format(
             "(player='%s' OR player='%s') AND player != ''",
             "(player='%s' OR player='%s') AND player != ''",
             sqlPage, sqlName),
             sqlPage, sqlName),
           orderBy = "tournament DESC", limit = 100 })
           orderBy = "tournament DESC", limit = 100 })
    indAwards = indAwards or {}


     -- ── 6b. Team tournament results ───────────────────────────────
     -- ── 6b. Team tournament results ───────────────────────────────
    -- Source 1: Tournament_Results — new invisible table, stores place per team per tournament
    -- Source 2: PrizeMoney — prize and award for team rows (player= empty)
    -- Shows ALL tournaments from participation, even if no prize/placement
     local teamResults = {}
     local teamResults = {}
     local seenKey    = {}
     local seenKey    = {}


     if participation and #participation > 0 then
     if #participation > 0 then
        -- Build unique tournament+team pairs
         local pairs_list = {}
         local pairs_list = {}
         for _, r in ipairs(participation) do
         for _, r in ipairs(participation) do
Line 204: Line 212:
         end
         end


         -- Build WHERE conditions for batch queries
         if #pairs_list > 0 then
        local ttConds = {}
            local ttConds = {}
        for _, r in ipairs(pairs_list) do
            for _, r in ipairs(pairs_list) do
            table.insert(ttConds, string.format(
                table.insert(ttConds, string.format(
                "(tournament='%s' AND team='%s')",
                    "(tournament='%s' AND team='%s')",
                esc(r.tournament), esc(r.team)))
                    esc(r.tournament), esc(r.team)))
        end
            end
 
        if #ttConds > 0 then
             local whereStr = table.concat(ttConds, " OR ")
             local whereStr = table.concat(ttConds, " OR ")


             -- Get placements from Tournament_Results table
             -- Placements from Tournament_Results table
             local placeIndex = {}
             local placeIndex = {}
             local placeRows = cargo.query("Tournament_Results",
             local placeRows = cargo.query("Tournament_Results",
                 "tournament,team,place",
                 "tournament,team,place",
                 { where = whereStr, limit = 500 })
                 { where = whereStr, limit = 500 })
Line 226: Line 232:
             end
             end


             -- Get prize + award from PrizeMoney (team rows only, player= empty)
             -- Prize + award from PrizeMoney (team rows only, no player)
             local prizeIndex = {}
             local prizeIndex = {}
             local prizeRows = cargo.query("PrizeMoney",
             local prizeRows = cargo.query("PrizeMoney",
                 "tournament,team,prize,placement,award",
                 "tournament,team,prize,placement,award",
                 { where = "(" .. whereStr .. ")"
                 { where = "(" .. whereStr .. ")"
Line 235: Line 241:
             if prizeRows then
             if prizeRows then
                 for _, row in ipairs(prizeRows) do
                 for _, row in ipairs(prizeRows) do
                     local k = row.tournament .. "|" .. row.team
                     local k       = row.tournament .. "|" .. row.team
                     -- keep highest prize row if multiple entries
                     local newPrize = tonumber(row.prize) or 0
                     local existing = prizeIndex[k]
                     local existing = prizeIndex[k]
                    local newPrize = tonumber(row.prize) or 0
                     if not existing or newPrize > (existing.prize or 0) then
                     if not existing or newPrize > (existing.prize or 0) then
                         prizeIndex[k] = {
                         prizeIndex[k] = {
Line 249: Line 254:
             end
             end


            -- Assemble final list
             for _, r in ipairs(pairs_list) do
             for _, r in ipairs(pairs_list) do
                 local k = r.tournament .. "|" .. r.team
                 local k     = r.tournament .. "|" .. r.team
                 local pr = prizeIndex[k]
                 local pr     = prizeIndex[k]
                 local pl_num = placeIndex[k]
                 local pl_num = placeIndex[k]


                 -- fallback: try to parse place from PrizeMoney placement string
                 -- fallback: parse number from PrizeMoney placement string
                 if not pl_num and pr and pr.placement ~= "" then
                 if not pl_num and pr and pr.placement ~= "" then
                     local n = pr.placement:match("(%d+)")
                     local n = pr.placement:match("(%d+)")
Line 265: Line 269:
                     team      = r.team,
                     team      = r.team,
                     place      = pl_num,
                     place      = pl_num,
                     award      = (pr and pr.award     ~= "" and pr.award)     or nil,
                     award      = (pr and pr.award ~= "" and pr.award) or nil,
                     prize      = (pr and pr.prize     > 0  and pr.prize)     or 0,
                     prize      = (pr and pr.prize > 0  and pr.prize) or 0,
                 })
                 })
             end
             end
Line 272: Line 276:
     end
     end


     -- Sort: placed entries first (by place), then unplaced
     -- Sort: placed first (ascending), then unplaced by tournament name desc
     table.sort(teamResults, function(a, b)
     table.sort(teamResults, function(a, b)
         if a.place and b.place then return a.place < b.place end
         if a.place and b.place then return a.place < b.place end
         if a.place then return true end
         if a.place then return true end
         if b.place then return false end
         if b.place then return false end
         return (a.tournament or "") > (b.tournament or "") -- recent first
         return (a.tournament or "") > (b.tournament or "")
     end)
     end)
    -- ── 7. Batch-fetch tournament metadata (tier + end_date) ──────
    -- Uses end_date, exactly like the achievements table does
    local allTournNames = {}
    local tournSeen    = {}
    local function addTourn(name)
        if name and name ~= "" and not tournSeen[name] then
            tournSeen[name] = true
            table.insert(allTournNames, "'" .. esc(name) .. "'")
        end
    end
    for _, a in ipairs(indAwards)  do addTourn(a.tournament) end
    for _, t in ipairs(teamResults) do addTourn(t.tournament) end
    local tournMeta = {}  -- [tournament name] = { tier, date }
    if #allTournNames > 0 then
        local metaRows = cargo.query("Tournaments",
            "name,tier,end_date",
            { where = "name IN (" .. table.concat(allTournNames, ",") .. ")",
              limit = 200 })
        if metaRows then
            for _, row in ipairs(metaRows) do
                tournMeta[row.name] = { tier = row.tier, date = row.end_date }
            end
        end
    end


     -- ── BUILD HTML ────────────────────────────────────────────────
     -- ── BUILD HTML ────────────────────────────────────────────────
Line 290: Line 320:
     local hero = root:tag('div'):addClass('pd-hero')
     local hero = root:tag('div'):addClass('pd-hero')


    -- Photo
     local photo = hero:tag('div'):addClass('pd-hero-photo')
     local photo = hero:tag('div'):addClass('pd-hero-photo')
     if pl.image and pl.image ~= "" then
     if pl.image and pl.image ~= "" then
         photo:wikitext("[[File:" .. pl.image
         photo:wikitext("[[File:" .. pl.image .. "|96px|link=|class=pd-hero-img]]")
            .. "|96px|link=|class=pd-hero-img]]")
     else
     else
         photo:wikitext(initials)
         photo:wikitext(initials)
     end
     end


    -- Name + tags
     local heroInfo = hero:tag('div'):addClass('pd-hero-info')
     local heroInfo = hero:tag('div'):addClass('pd-hero-info')
     heroInfo:tag('div'):addClass('pd-hero-name'):wikitext(displayName)
     heroInfo:tag('div'):addClass('pd-hero-name'):wikitext(displayName)
Line 325: Line 352:
     end
     end


    -- Earnings
     if totalEarnings > 0 then
     if totalEarnings > 0 then
         local er = hero:tag('div'):addClass('pd-hero-right')
         local er = hero:tag('div'):addClass('pd-hero-right')
Line 362: Line 388:
     infoRow('Game', pl.game)
     infoRow('Game', pl.game)


     -- Current team with logo + display_name
     -- Current team: logo + display_name
     if pl.current_team and pl.current_team ~= "" then
     if pl.current_team and pl.current_team ~= "" then
         local ti          = getTeamInfo(pl.current_team)
         local ti          = getTeamInfo(pl.current_team)
Line 368: Line 394:
             and ti.display_name or pl.current_team
             and ti.display_name or pl.current_team
         local logoHtml    = teamLogoWikitext(ti, "22px")
         local logoHtml    = teamLogoWikitext(ti, "22px")
 
         local row         = left:tag('div'):addClass('pd-info-row')
         local row = left:tag('div'):addClass('pd-info-row')
         row:tag('span'):addClass('pd-info-label'):wikitext('Current Team')
         row:tag('span'):addClass('pd-info-label'):wikitext('Current Team')
         local val  = row:tag('span'):addClass('pd-info-val')
         local wrap = row:tag('span'):addClass('pd-info-val')
        local wrap = val:tag('span'):addClass('pd-current-team')
            :tag('span'):addClass('pd-current-team')
         if logoHtml then wrap:wikitext(logoHtml) end
         if logoHtml then wrap:wikitext(logoHtml) end
         wrap:tag('span'):addClass('pd-current-team-name')
         wrap:tag('span'):addClass('pd-current-team-name')
Line 403: Line 428:
             :wikitext('Team History')
             :wikitext('Team History')
         for _, h in ipairs(teamHist) do
         for _, h in ipairs(teamHist) do
             local ti     = getTeamInfo(h.team or "")
             local ti       = getTeamInfo(h.team or "")
             local logoHtml = teamLogoWikitext(ti, "24px")
             local logoHtml = teamLogoWikitext(ti, "24px")
             local tr     = left:tag('div'):addClass('pd-team-row')
             local tr       = left:tag('div'):addClass('pd-team-row')
 
             if logoHtml then
             if logoHtml then
                 tr:wikitext(logoHtml)
                 tr:wikitext(logoHtml)
Line 413: Line 437:
                     :wikitext((h.team or "?"):sub(1, 3):upper())
                     :wikitext((h.team or "?"):sub(1, 3):upper())
             end
             end
             tr:tag('div'):addClass('pd-team-name')
             tr:tag('div'):addClass('pd-team-name')
                 :wikitext("[[" .. (h.team or "") .. "]]")
                 :wikitext("[[" .. (h.team or "") .. "]]")
Line 427: Line 450:
     local right = body:tag('div'):addClass('pd-right')
     local right = body:tag('div'):addClass('pd-right')


     -- Individual awards table
     -- Shared cell renderers (match achievements table exactly)
     if indAwards and #indAwards > 0 then
     local function renderTierCell(td, tier)
        if tier and tier ~= "" then
            td:addClass('ac-tier')
              :tag('span'):addClass('tier-badge ' .. getTierClass(tier)):wikitext(tier)
        else
            td:wikitext("—")
        end
    end
 
    local function renderDateCell(td, date)
        -- achievements table shows raw end_date string (e.g. "2025-03-15")
        td:addClass('ac-date'):wikitext(date and date ~= "" and date or "—")
    end
 
    -- ── Individual Awards ─────────────────────────────────────────
    -- Columns: Date | Tier | Tournament | Team | Award | Prize
    if #indAwards > 0 then
         local sec = right:tag('div'):addClass('pd-prize-section')
         local sec = right:tag('div'):addClass('pd-prize-section')
         sec:tag('div'):addClass('pd-table-title'):wikitext('🏆 Individual Awards')
         sec:tag('div'):addClass('pd-table-title'):wikitext('🏆 Individual Awards')
         local tbl = sec:tag('table'):addClass('pd-tourn-table')
         local tbl = sec:tag('table'):addClass('pd-tourn-table')
         local hdr = tbl:tag('tr')
         local hdr = tbl:tag('tr')
        hdr:tag('th'):wikitext('Date')
        hdr:tag('th'):wikitext('Tier')
         hdr:tag('th'):wikitext('Tournament')
         hdr:tag('th'):wikitext('Tournament')
        hdr:tag('th'):wikitext('Team')
         hdr:tag('th'):wikitext('Award')
         hdr:tag('th'):wikitext('Award')
         hdr:tag('th'):wikitext('Prize')
         hdr:tag('th'):css('text-align','right'):wikitext('Prize')
 
         for _, a in ipairs(indAwards) do
         for _, a in ipairs(indAwards) do
             local tr = tbl:tag('tr')
            local meta = tournMeta[a.tournament or ""] or {}
             tr:tag('td'):wikitext("[[" .. (a.tournament or "") .. "]]")
             local tr   = tbl:tag('tr')
            renderDateCell(tr:tag('td'), meta.date)
            renderTierCell(tr:tag('td'), meta.tier)
             tr:tag('td'):css('font-weight','600')
                :wikitext("[[" .. (a.tournament or "") .. "]]")
            -- Team column: which team was the player on during this tournament
            local playerTeam = tournTeamMap[a.tournament or ""]
            tr:tag('td'):wikitext(playerTeam and ("[[" .. playerTeam .. "]]") or "—")
             tr:tag('td'):tag('span'):addClass('pd-award-badge')
             tr:tag('td'):tag('span'):addClass('pd-award-badge')
                 :wikitext(a.award and a.award ~= "" and a.award or "Award")
                 :wikitext(a.award and a.award ~= "" and a.award or "Award")
             tr:tag('td'):addClass('pd-prize-val')
             tr:tag('td'):addClass('ac-prize')
                 :wikitext(tonumber(a.prize) and tonumber(a.prize) > 0
                 :wikitext(tonumber(a.prize) and tonumber(a.prize) > 0
                     and fmtCurrency(tonumber(a.prize)) or "—")
                     and fmtCurrency(tonumber(a.prize)) or "—")
Line 447: Line 497:
     end
     end


     -- Team results table
     -- ── Team Tournament Results ───────────────────────────────────
    -- Columns: Date | Tier | Tournament | Team | Place/Award | Prize
     local sec2 = right:tag('div'):addClass('pd-prize-section')
     local sec2 = right:tag('div'):addClass('pd-prize-section')
     sec2:tag('div'):addClass('pd-table-title'):wikitext('🎖 Team Tournament Results')
     sec2:tag('div'):addClass('pd-table-title'):wikitext('🎖 Team Tournament Results')
Line 454: Line 505:
         local tbl2 = sec2:tag('table'):addClass('pd-tourn-table')
         local tbl2 = sec2:tag('table'):addClass('pd-tourn-table')
         local hdr2 = tbl2:tag('tr')
         local hdr2 = tbl2:tag('tr')
        hdr2:tag('th'):wikitext('Date')
        hdr2:tag('th'):wikitext('Tier')
         hdr2:tag('th'):wikitext('Tournament')
         hdr2:tag('th'):wikitext('Tournament')
         hdr2:tag('th'):wikitext('Team')
         hdr2:tag('th'):wikitext('Team')
         hdr2:tag('th'):wikitext('Place / Award') -- combined heading
         hdr2:tag('th'):addClass('ac-place'):wikitext('Place / Award')
         hdr2:tag('th'):wikitext('Prize')
         hdr2:tag('th'):css('text-align','right'):wikitext('Prize')


         for _, t in ipairs(teamResults) do
         for _, t in ipairs(teamResults) do
             local tr2 = tbl2:tag('tr')
            local meta = tournMeta[t.tournament or ""] or {}
             tr2:tag('td'):wikitext("[[" .. t.tournament .. "]]")
             local tr2 = tbl2:tag('tr')
             renderDateCell(tr2:tag('td'), meta.date)
            renderTierCell(tr2:tag('td'), meta.tier)
            tr2:tag('td'):css('font-weight','600')
                :wikitext("[[" .. t.tournament .. "]]")
             tr2:tag('td'):wikitext("[[" .. t.team .. "]]")
             tr2:tag('td'):wikitext("[[" .. t.team .. "]]")


            -- Place chip OR award badge OR dash
             local pTd = tr2:tag('td'):addClass('ac-place')
             local placeTd = tr2:tag('td')
             if t.place then
             if t.place then
                 local pc = t.place == 1 and 'pd-place-chip place-1'
                 renderPlaceBadge(pTd, t.place)
                        or t.place == 2 and 'pd-place-chip place-2'
                        or t.place == 3 and 'pd-place-chip place-3'
                        or 'pd-place-chip place-other'
                placeTd:tag('span'):addClass(pc):wikitext(tostring(t.place))
             elseif t.award then
             elseif t.award then
                 placeTd:tag('span'):addClass('pd-award-badge'):wikitext(t.award)
                 pTd:tag('span'):addClass('pd-award-badge'):wikitext(t.award)
             else
             else
                 placeTd:wikitext("—")
                 pTd:wikitext("—")
             end
             end


             tr2:tag('td')
             tr2:tag('td'):addClass('ac-prize')
                :addClass(t.prize > 0 and 'pd-prize-val' or '')
                 :wikitext(t.prize > 0 and fmtCurrency(t.prize) or "—")
                 :wikitext(t.prize > 0 and fmtCurrency(t.prize) or "—")
         end
         end

Revision as of 04:32, 7 March 2026

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

-- ================================================================
-- Module:PlayerDashboard v3
-- ================================================================

local p     = {}
local cargo = mw.ext.cargo
local html  = mw.html
local lang  = mw.getContentLanguage()

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

local function esc(s)
    if not s then return "" end
    return s:gsub("\\", "\\\\"):gsub("'", "\\'")
end

local function fmtCurrency(n)
    if not n or n == 0 then return "—" end
    local s    = tostring(math.floor(n))
    local l3   = s:sub(-3)
    local rest = s:sub(1, -4)
    local fmt  = rest:reverse():gsub("(%d%d)", "%1,"):reverse()
    if fmt ~= "" then l3 = "," .. l3 end
    return "₹ " .. fmt .. l3
end

local function fmtDate(d)
    if not d or d == "" then return "?" end
    return lang:formatDate("M y", d)
end

-- Matches getTierClass used in Module:Team and Module:Tournament
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

-- Flag emoji map
local FLAGS = {
    india="🇮🇳", bangladesh="🇧🇩", pakistan="🇵🇰", nepal="🇳🇵",
    srilanka="🇱🇰", myanmar="🇲🇲", indonesia="🇮🇩", thailand="🇹🇭",
    malaysia="🇲🇾", philippines="🇵🇭", vietnam="🇻🇳", singapore="🇸🇬",
    china="🇨🇳", japan="🇯🇵", korea="🇰🇷", usa="🇺🇸",
    uk="🇬🇧", germany="🇩🇪", brazil="🇧🇷", australia="🇦🇺"
}
local function getFlag(country)
    if not country or country == "" then return "" end
    local key = country:lower():gsub("%s+",""):gsub("-","")
    return (FLAGS[key] or "") .. " "
end

local function statusStyle(s)
    s = (s or ""):lower()
    if s == "active"   then return "#4ade80","rgba(34,197,94,.2)",  "rgba(34,197,94,.35)"   end
    if s == "inactive" then return "#fbbf24","rgba(251,191,36,.2)", "rgba(251,191,36,.35)"  end
    if s == "retired"  then return "#94a3b8","rgba(148,163,184,.2)","rgba(148,163,184,.35)" end
    if s == "banned"   then return "#f87171","rgba(239,68,68,.2)",  "rgba(239,68,68,.35)"   end
    return "#94a3b8","rgba(148,163,184,.2)","rgba(148,163,184,.35)"
end

-- ── Team info + logo helpers ──────────────────────────────────────

local teamInfoCache = {}
local function getTeamInfo(teamName)
    if not teamName or teamName == "" then return nil end
    if teamInfoCache[teamName] then return teamInfoCache[teamName] end
    local rows = cargo.query("Teams",
        "display_name,image,image_dark",
        { where = "name='" .. esc(teamName) .. "'", limit = 1 })
    local result = (rows and #rows > 0) and rows[1] or nil
    teamInfoCache[teamName] = result
    return result
end

-- Team logo: outputs both light + dark versions, CSS switches them
-- Uses your existing .logo-lightmode / .logo-darkmode classes
local function teamLogoWikitext(ti, size)
    if not ti then return nil end
    size = size or "26px"
    local light = (ti.image      and ti.image      ~= "") and ti.image      or nil
    local dark  = (ti.image_dark and ti.image_dark ~= "") and ti.image_dark or light
    if not light then return nil end
    return "[[File:"..light.."|"..size.."|link=|class=pd-team-logo-img logo-lightmode]]"
        .. "[[File:"..dark .."|"..size.."|link=|class=pd-team-logo-img logo-darkmode]]"
end

-- Place badge — matches achievements table style exactly
-- (uses .place-badge, .place-1, .place-2, .place-3, .place-def)
local function renderPlaceBadge(td, placeNum)
    local badgeClass = "place-def"
    local placeText  = tostring(placeNum) .. "th"
    if placeNum == 1 then badgeClass = "place-1"; placeText = "1st"
    elseif placeNum == 2 then badgeClass = "place-2"; placeText = "2nd"
    elseif placeNum == 3 then badgeClass = "place-3"; placeText = "3rd"
    end
    td:tag('span'):addClass('place-badge ' .. badgeClass):wikitext(placeText)
end

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

function p.main(frame)
    local pageName = mw.title.getCurrentTitle().text
    local sqlPage  = esc(pageName)

    -- ── 1. Player row ─────────────────────────────────────────────
    local pRows = cargo.query("Players",
        "id,real_name,image,current_team,nationality,status,game,"
        .. "birth_date,role,instagram,youtube,twitter,discord,facebook",
        { where = "_pageName='" .. sqlPage .. "'", limit = 1 })
    local pl          = (pRows and #pRows > 0) and pRows[1] or {}
    local displayName = pl.id or mw.title.getCurrentTitle().subpageText
    local sqlName     = esc(displayName)

    -- ── 2. Krafton rank ───────────────────────────────────────────
    local rankVal = nil
    local rRows = cargo.query("Krafton_Rankings", "rank", {
        where = string.format(
            "(name='%s' OR name='%s') AND type='Player'", sqlPage, sqlName),
        limit = 1 })
    if rRows and #rRows > 0 then rankVal = "#" .. rRows[1].rank end

    -- ── 3. All tournaments player appeared in ─────────────────────
    local participation = cargo.query("Tournament_Teams", "tournament,team", {
        where = string.format(
            "p1='%s' OR p1='%s' OR p2='%s' OR p2='%s' OR "
            .."p3='%s' OR p3='%s' OR p4='%s' OR p4='%s' OR "
            .."p5='%s' OR p5='%s' OR p6='%s' OR p6='%s'",
            sqlPage,sqlName, sqlPage,sqlName, sqlPage,sqlName,
            sqlPage,sqlName, sqlPage,sqlName, sqlPage,sqlName),
        limit = 500 })
    participation = participation or {}

    -- Quick lookup: tournament -> team the player was on (for individual awards)
    local tournTeamMap = {}
    for _, r in ipairs(participation) do
        if r.tournament and r.team and not tournTeamMap[r.tournament] then
            tournTeamMap[r.tournament] = r.team
        end
    end

    -- ── 4. Earnings ───────────────────────────────────────────────
    local indEarnings, teamEarnings = 0, 0

    local eRows = cargo.query("PrizeMoney", "SUM(prize)=total", {
        where = string.format(
            "(player='%s' OR player='%s') AND player != ''", sqlPage, sqlName) })
    if eRows and #eRows > 0 then
        indEarnings = tonumber(eRows[1].total) or 0
    end

    if #participation > 0 then
        local conds = {}
        for _, r in ipairs(participation) do
            if r.tournament and r.team then
                table.insert(conds, string.format(
                    "(tournament='%s' AND team='%s')",
                    esc(r.tournament), esc(r.team)))
            end
        end
        if #conds > 0 then
            local tPrizes = cargo.query("PrizeMoney", "SUM(prize)=team_total", {
                where = "(" .. table.concat(conds, " OR ") .. ")"
                    .. " AND (player='' OR player IS NULL)" })
            if tPrizes and #tPrizes > 0 then
                teamEarnings = tonumber(tPrizes[1].team_total) or 0
            end
        end
    end
    local totalEarnings = indEarnings + teamEarnings

    -- ── 5. Team history ───────────────────────────────────────────
    local teamHist = cargo.query("Player_Former_Teams",
        "team,join_date,leave_date",
        { where = "player_id='" .. sqlPage .. "'",
          orderBy = "join_date DESC", limit = 20 })
    if not teamHist or #teamHist == 0 then
        teamHist = cargo.query("Player_Former_Teams",
            "team,join_date,leave_date",
            { where = "player_id='" .. sqlName .. "'",
              orderBy = "join_date DESC", limit = 20 })
    end
    teamHist = teamHist or {}

    -- ── 6a. Individual awards ─────────────────────────────────────
    local indAwards = cargo.query("PrizeMoney",
        "tournament,award,prize",
        { where = string.format(
            "(player='%s' OR player='%s') AND player != ''",
            sqlPage, sqlName),
          orderBy = "tournament DESC", limit = 100 })
    indAwards = indAwards or {}

    -- ── 6b. Team tournament results ───────────────────────────────
    local teamResults = {}
    local seenKey     = {}

    if #participation > 0 then
        local pairs_list = {}
        for _, r in ipairs(participation) do
            if r.tournament and r.team then
                local k = r.tournament .. "|" .. r.team
                if not seenKey[k] then
                    seenKey[k] = true
                    table.insert(pairs_list, { tournament = r.tournament, team = r.team })
                end
            end
        end

        if #pairs_list > 0 then
            local ttConds = {}
            for _, r in ipairs(pairs_list) do
                table.insert(ttConds, string.format(
                    "(tournament='%s' AND team='%s')",
                    esc(r.tournament), esc(r.team)))
            end
            local whereStr = table.concat(ttConds, " OR ")

            -- Placements from Tournament_Results table
            local placeIndex = {}
            local placeRows  = cargo.query("Tournament_Results",
                "tournament,team,place",
                { where = whereStr, limit = 500 })
            if placeRows then
                for _, row in ipairs(placeRows) do
                    placeIndex[row.tournament .. "|" .. row.team] = tonumber(row.place)
                end
            end

            -- Prize + award from PrizeMoney (team rows only, no player)
            local prizeIndex = {}
            local prizeRows  = cargo.query("PrizeMoney",
                "tournament,team,prize,placement,award",
                { where = "(" .. whereStr .. ")"
                    .. " AND (player='' OR player IS NULL)",
                  limit = 500 })
            if prizeRows then
                for _, row in ipairs(prizeRows) do
                    local k        = row.tournament .. "|" .. row.team
                    local newPrize = tonumber(row.prize) or 0
                    local existing = prizeIndex[k]
                    if not existing or newPrize > (existing.prize or 0) then
                        prizeIndex[k] = {
                            prize     = newPrize,
                            placement = row.placement or "",
                            award     = row.award     or "",
                        }
                    end
                end
            end

            for _, r in ipairs(pairs_list) do
                local k      = r.tournament .. "|" .. r.team
                local pr     = prizeIndex[k]
                local pl_num = placeIndex[k]

                -- fallback: parse number from PrizeMoney placement string
                if not pl_num and pr and pr.placement ~= "" then
                    local n = pr.placement:match("(%d+)")
                    if n then pl_num = tonumber(n) end
                end

                table.insert(teamResults, {
                    tournament = r.tournament,
                    team       = r.team,
                    place      = pl_num,
                    award      = (pr and pr.award ~= "" and pr.award) or nil,
                    prize      = (pr and pr.prize  > 0  and pr.prize) or 0,
                })
            end
        end
    end

    -- Sort: placed first (ascending), then unplaced by tournament name desc
    table.sort(teamResults, function(a, b)
        if a.place and b.place then return a.place < b.place end
        if a.place  then return true  end
        if b.place  then return false end
        return (a.tournament or "") > (b.tournament or "")
    end)

    -- ── 7. Batch-fetch tournament metadata (tier + end_date) ──────
    -- Uses end_date, exactly like the achievements table does
    local allTournNames = {}
    local tournSeen     = {}
    local function addTourn(name)
        if name and name ~= "" and not tournSeen[name] then
            tournSeen[name] = true
            table.insert(allTournNames, "'" .. esc(name) .. "'")
        end
    end
    for _, a in ipairs(indAwards)   do addTourn(a.tournament) end
    for _, t in ipairs(teamResults) do addTourn(t.tournament) end

    local tournMeta = {}  -- [tournament name] = { tier, date }
    if #allTournNames > 0 then
        local metaRows = cargo.query("Tournaments",
            "name,tier,end_date",
            { where = "name IN (" .. table.concat(allTournNames, ",") .. ")",
              limit = 200 })
        if metaRows then
            for _, row in ipairs(metaRows) do
                tournMeta[row.name] = { tier = row.tier, date = row.end_date }
            end
        end
    end

    -- ── BUILD HTML ────────────────────────────────────────────────

    local sc, sbg, sborder = statusStyle(pl.status)
    local initials = displayName:sub(1, 2):upper()
    local root     = html.create('div'):addClass('pd-dashboard')

    -- ════ HERO ════

    local hero = root:tag('div'):addClass('pd-hero')

    local photo = hero:tag('div'):addClass('pd-hero-photo')
    if pl.image and pl.image ~= "" then
        photo:wikitext("[[File:" .. pl.image .. "|96px|link=|class=pd-hero-img]]")
    else
        photo:wikitext(initials)
    end

    local heroInfo = hero:tag('div'):addClass('pd-hero-info')
    heroInfo:tag('div'):addClass('pd-hero-name'):wikitext(displayName)
    if pl.real_name and pl.real_name ~= "" then
        heroInfo:tag('div'):addClass('pd-hero-realname'):wikitext(pl.real_name)
    end

    local tags = heroInfo:tag('div'):addClass('pd-hero-tags')
    if pl.status and pl.status ~= "" then
        tags:tag('span'):addClass('pd-tag')
            :css('color', sc):css('background', sbg)
            :css('border', '1px solid ' .. sborder)
            :wikitext('● ' .. pl.status)
    end
    if pl.role and pl.role ~= "" then
        tags:tag('span'):addClass('pd-tag pd-tag-role'):wikitext(pl.role)
    end
    if pl.game and pl.game ~= "" then
        tags:tag('span'):addClass('pd-tag pd-tag-game'):wikitext(pl.game)
    end
    if rankVal then
        heroInfo:tag('div'):addClass('pd-rank-chip')
            :tag('span'):wikitext('Krafton Rank'):done()
            :tag('b'):wikitext(rankVal)
    end

    if totalEarnings > 0 then
        local er = hero:tag('div'):addClass('pd-hero-right')
        er:tag('div'):addClass('pd-earnings-label'):wikitext('Approx. Earnings')
        er:tag('div'):addClass('pd-earnings-val'):wikitext(fmtCurrency(totalEarnings))
        local bd = er:tag('div'):addClass('pd-earnings-breakdown')
        bd:tag('div')
            :tag('div'):addClass('pd-earn-sub-label'):wikitext('Team'):done()
            :tag('div'):addClass('pd-earn-sub-val'):wikitext(fmtCurrency(teamEarnings))
        bd:tag('div')
            :tag('div'):addClass('pd-earn-sub-label'):wikitext('Individual'):done()
            :tag('div'):addClass('pd-earn-sub-val'):wikitext(fmtCurrency(indEarnings))
    end

    -- ════ BODY ════

    local body = root:tag('div'):addClass('pd-body')
    local left = body:tag('div'):addClass('pd-left')
    left:tag('div'):addClass('pd-section-title'):wikitext('Player Info')

    local function infoRow(label, val)
        if not val or val == "" then return end
        left:tag('div'):addClass('pd-info-row')
            :tag('span'):addClass('pd-info-label'):wikitext(label):done()
            :tag('span'):addClass('pd-info-val'):wikitext(val)
    end

    infoRow('Real Name', pl.real_name)
    if pl.birth_date and pl.birth_date ~= "" then
        infoRow('Born', lang:formatDate("d F Y", pl.birth_date))
    end
    if pl.nationality and pl.nationality ~= "" then
        infoRow('Nationality', getFlag(pl.nationality) .. pl.nationality)
    end
    infoRow('Role', pl.role)
    infoRow('Game', pl.game)

    -- Current team: logo + display_name
    if pl.current_team and pl.current_team ~= "" then
        local ti          = getTeamInfo(pl.current_team)
        local teamDisplay = (ti and ti.display_name and ti.display_name ~= "")
            and ti.display_name or pl.current_team
        local logoHtml    = teamLogoWikitext(ti, "22px")
        local row         = left:tag('div'):addClass('pd-info-row')
        row:tag('span'):addClass('pd-info-label'):wikitext('Current Team')
        local wrap = row:tag('span'):addClass('pd-info-val')
            :tag('span'):addClass('pd-current-team')
        if logoHtml then wrap:wikitext(logoHtml) end
        wrap:tag('span'):addClass('pd-current-team-name')
            :wikitext("[[" .. pl.current_team .. "|" .. teamDisplay .. "]]")
    end

    if rankVal then infoRow('Krafton Rank', rankVal) end

    -- Socials
    local socials = {
        { k='instagram', file='Icon_instagram.png', base='https://instagram.com/' },
        { k='twitter',   file='Icon_twitter.png',   base='https://twitter.com/'   },
        { k='youtube',   file='Icon_youtube.png',   base='https://youtube.com/'   },
        { k='discord',   file='Icon_discord.png',   base='https://discord.gg/'    },
        { k='facebook',  file='Icon_facebook.png',  base='https://facebook.com/'  },
    }
    local socDiv = left:tag('div'):addClass('pd-socials')
    for _, s in ipairs(socials) do
        local v = pl[s.k]
        if v and v ~= "" then
            local url = v:match("^https?://") and v or (s.base .. v)
            socDiv:wikitext("[[File:" .. s.file
                .. "|32px|link=" .. url .. "|class=social-img]]")
        end
    end

    -- Team history with logos
    if #teamHist > 0 then
        left:tag('div'):addClass('pd-section-title pd-section-gap')
            :wikitext('Team History')
        for _, h in ipairs(teamHist) do
            local ti       = getTeamInfo(h.team or "")
            local logoHtml = teamLogoWikitext(ti, "24px")
            local tr       = left:tag('div'):addClass('pd-team-row')
            if logoHtml then
                tr:wikitext(logoHtml)
            else
                tr:tag('div'):addClass('pd-team-logo-fb')
                    :wikitext((h.team or "?"):sub(1, 3):upper())
            end
            tr:tag('div'):addClass('pd-team-name')
                :wikitext("[[" .. (h.team or "") .. "]]")
            tr:tag('div'):addClass('pd-team-dates')
                :wikitext(fmtDate(h.join_date) .. " – "
                    .. (h.leave_date and h.leave_date ~= ""
                        and fmtDate(h.leave_date) or "Now"))
        end
    end

    -- ════ RIGHT PANEL ════

    local right = body:tag('div'):addClass('pd-right')

    -- Shared cell renderers (match achievements table exactly)
    local function renderTierCell(td, tier)
        if tier and tier ~= "" then
            td:addClass('ac-tier')
              :tag('span'):addClass('tier-badge ' .. getTierClass(tier)):wikitext(tier)
        else
            td:wikitext("—")
        end
    end

    local function renderDateCell(td, date)
        -- achievements table shows raw end_date string (e.g. "2025-03-15")
        td:addClass('ac-date'):wikitext(date and date ~= "" and date or "—")
    end

    -- ── Individual Awards ─────────────────────────────────────────
    -- Columns: Date | Tier | Tournament | Team | Award | Prize
    if #indAwards > 0 then
        local sec = right:tag('div'):addClass('pd-prize-section')
        sec:tag('div'):addClass('pd-table-title'):wikitext('🏆 Individual Awards')
        local tbl = sec:tag('table'):addClass('pd-tourn-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('Team')
        hdr:tag('th'):wikitext('Award')
        hdr:tag('th'):css('text-align','right'):wikitext('Prize')

        for _, a in ipairs(indAwards) do
            local meta = tournMeta[a.tournament or ""] or {}
            local tr   = tbl:tag('tr')
            renderDateCell(tr:tag('td'), meta.date)
            renderTierCell(tr:tag('td'), meta.tier)
            tr:tag('td'):css('font-weight','600')
                :wikitext("[[" .. (a.tournament or "") .. "]]")
            -- Team column: which team was the player on during this tournament
            local playerTeam = tournTeamMap[a.tournament or ""]
            tr:tag('td'):wikitext(playerTeam and ("[[" .. playerTeam .. "]]") or "—")
            tr:tag('td'):tag('span'):addClass('pd-award-badge')
                :wikitext(a.award and a.award ~= "" and a.award or "Award")
            tr:tag('td'):addClass('ac-prize')
                :wikitext(tonumber(a.prize) and tonumber(a.prize) > 0
                    and fmtCurrency(tonumber(a.prize)) or "—")
        end
    end

    -- ── Team Tournament Results ───────────────────────────────────
    -- Columns: Date | Tier | Tournament | Team | Place/Award | Prize
    local sec2 = right:tag('div'):addClass('pd-prize-section')
    sec2:tag('div'):addClass('pd-table-title'):wikitext('🎖 Team Tournament Results')

    if #teamResults > 0 then
        local tbl2 = sec2:tag('table'):addClass('pd-tourn-table')
        local hdr2 = tbl2:tag('tr')
        hdr2:tag('th'):wikitext('Date')
        hdr2:tag('th'):wikitext('Tier')
        hdr2:tag('th'):wikitext('Tournament')
        hdr2:tag('th'):wikitext('Team')
        hdr2:tag('th'):addClass('ac-place'):wikitext('Place / Award')
        hdr2:tag('th'):css('text-align','right'):wikitext('Prize')

        for _, t in ipairs(teamResults) do
            local meta = tournMeta[t.tournament or ""] or {}
            local tr2  = tbl2:tag('tr')
            renderDateCell(tr2:tag('td'), meta.date)
            renderTierCell(tr2:tag('td'), meta.tier)
            tr2:tag('td'):css('font-weight','600')
                :wikitext("[[" .. t.tournament .. "]]")
            tr2:tag('td'):wikitext("[[" .. t.team .. "]]")

            local pTd = tr2:tag('td'):addClass('ac-place')
            if t.place then
                renderPlaceBadge(pTd, t.place)
            elseif t.award then
                pTd:tag('span'):addClass('pd-award-badge'):wikitext(t.award)
            else
                pTd:wikitext("—")
            end

            tr2:tag('td'):addClass('ac-prize')
                :wikitext(t.prize > 0 and fmtCurrency(t.prize) or "—")
        end
    else
        sec2:tag('div'):addClass('pd-empty')
            :wikitext('No tournament data found.')
    end

    -- Stats placeholder
    right:tag('div'):addClass('pd-stats-placeholder')
        :tag('b'):wikitext('📊 Performance Stats'):done()
        :wikitext('Coming soon — per-match stats will appear here.')

    return tostring(root)
end

return p