Module:Bracket

local util_args = require('Module:ArgsUtil') local util_cargo = require('Module:CargoUtil') local util_esports = require('Module:EsportsUtil') local util_table = require('Module:TableUtil') local util_text = require('Module:TextUtil') local util_vars = require('Module:VarsUtil') local bracket_wiki = require('Module:Bracket/Wiki') -- wiki localization per game

local m_team = require('Module:Team') local lang = mw.getLanguage('en')

local ROWS_PER_TEAM = 6 local ROWS_PER_TITLE = 2 local ROWS_PER_HLINE = 1 local ROUNDWIDTH = 12 local LINEWIDTH = '3em' local SCOREWIDTH = 2

local sep = '%s*,%s*'

local h = {}

function h.processArgs(tpl_args) -- format tpl_args local args = {} for k, v in pairs(tpl_args) do		if type(k) ~= 'string' then -- pass elseif k:find('R%d+M%d+_.*_') then local r, m, val, team = k:match('R(%d+)M(%d+)_(.*)_(%d+)') r = tonumber(r) m = tonumber(m) h.initializeMatch(args, r, m)			if val == 'team' then args[r][m]['team' .. team][val] = m_team.teamlinkname(v) else args[r][m]['team' .. team][val] = v			end elseif k:find('R%d+M%d+_.*') then local r, m, val = k:match('R(%d+)M(%d+)_(.*)') r = tonumber(r) m = tonumber(m) h.initializeMatch(args, r, m)			args[r][m][val] = v		elseif k:find('R%d+_') then local r, val = k:match('R(%d+)_(.*)') r = tonumber(r) h.initializeMatch(args, r)			args[r][val] = v		else args[k] = v		end end return args end

function h.initializeMatch(args, r, m)	if not args[r] then args[r] = {} end if not args[r][m] and m then args[r][m] = { team1 = {}, team2 = {} } end end

function h.processSettings(settings, args) -- in theory this could be done in the settings module before returning but -- this way the code is a bit more hidden from users editing stuff -- and also this makes the settings module closer to a read-only table that you -- import (and clone) here which i guess is nice? -- tbh im not sure if this was the right way to do it tho for r, col in ipairs(settings) do		local m = #col.matches while m >= 1 do			-- need to iterate backwards bc we'll delete third-place matches if hidden local match = col.matches[m] local lines = col.lines and col.lines[m] if lines and lines.reseed then lines.class = lines.class:format(lang:lc(args.reseed or 'reseeding')) end if match.argtoshow then if not util_args.castAsBool(args[match.argtoshow]) then if col.matches[m+1] then col.matches[m+1].above = (col.matches[m+1].above or 0) + (match.above or 0) + 6 end table.remove(col.matches,m) end end m = m - 1 end end end

-- cargo function h.addCargoData(args, settings) local overviewPage = util_esports.getOverviewPage(args.page) local data = h.doCargoQuery(overviewPage) if not next(data) then return end local processed = h.processCargoData(data) h.addProcessedToArgs(args, settings, processed, overviewPage) end

function h.doCargoQuery(page) local query = { tables = 'MatchSchedule', fields = { 'Team1', 'Team2', 'Team1Final', 'Team2Final', 'Winner', 'FF', 'Team1Score', 'Team2Score', 'Tab', 'N_MatchInTab', 'UniqueMatch' },		where = ('OverviewPage="%s"'):format(page), types = { -- keep winner as a string since that's what's expected from args sigh Team1Score = 'number', Team2Score = 'number', FF = 'number' },		limit = 9999 }	return util_cargo.queryAndCast(query) end

function h.processCargoData(data) local processed = {} for _, row in ipairs(data) do		h.sortFF(row) processed[('%s_%s'):format(row.Tab,row.N_MatchInTab)] = { winner = row.Winner, team1 = { score = row.Team1Score, team = row.Team1, teamfinal = row.Team1Final }, team2 = { score = row.Team2Score, team = row.Team2, teamfinal = row.Team2Final }, }	end return processed end

function h.sortFF(row) if row.FF == 1 then row.Team1Score = 'FF' row.Team2Score = 'W'	elseif row.FF == 2 then row.Team1Score = 'W'		row.Team2Score = 'FF' end end

function h.addProcessedToArgs(args, settings, processed, overviewPage) for r, col in ipairs(settings) do		h.initializeMatch(args, r)		local title = args[r] and args[r].title or col.matches.title or '' for m, _ in ipairs(col.matches) do			h.initializeMatch(args, r, m)			local argmatch = args[r] and args[r][m] if argmatch and argmatch.cargomatch then h.addMatchCargoToMatch(argmatch, processed[argmatch.cargomatch]) else -- the uniquematch does NOT include page number in it				local uniquematch = ('%s_%s'):format(title, m)				if not argmatch then h.initializeMatch(args, r, m)					argmatch = args[r][m] end h.addMatchCargoToMatch(argmatch, processed[uniquematch]) end end end end

function h.addMatchCargoToMatch(argMatch, cargoDataMatch) if not cargoDataMatch then return end -- allow arg data to overwrite cargo data always if applicable argMatch.winner = argMatch.winner or cargoDataMatch.winner for _, team in ipairs({ 'team1', 'team2' }) do		for k, v in pairs(cargoDataMatch[team]) do			argMatch[team][k] = argMatch[team][k] or v		end end end

-- print function h.makeOutput(args, settings) local output = mw.html.create if settings.togglers then h.printAllBrackets(args, settings, output) else h.printBracket(args, settings, output:tag('div'), {}) end return output end

function h.printAllBrackets(args, settings, output) local toggleN = util_vars.setGlobalIndex('BracketToggler') local togglers = h.makeTogglerButtons(settings.togglers, toggleN) local tblRound1 = h.printNextBracketDiv(output, toggleN, 1) h.printBracket(args, settings, tblRound1, togglers) local tableList = { tblRound1 } for i, toggle in ipairs(settings.togglers) do		h.setupNextToggle(settings, args, togglers, toggle, i)		local tbl = h.printNextBracketDiv(output, toggleN, i + 1) h.printBracket(args, toggle.bracket, tbl, togglers) tableList[#tableList+1] = tbl end h.setTableHidden(tableList, args.initround) end

function h.setupNextToggle(settings, args, togglers, toggle, i)	h.fixColumnLabelsForToggle(settings, toggle.bracket, i)	table.remove(args, 1) table.remove(togglers, 1) h.processSettings(toggle.bracket, args) end

function h.fixColumnLabelsForToggle(settings, bracket, i)	for k, col in ipairs(bracket) do		col.matches.title = settings[k + i].matches.title end end

function h.printNextBracketDiv(output, toggleN, i)	local div = output:tag('div') :addClass(h.allToggleClass(toggleN, false)) :addClass(h.roundToggleClass(toggleN, i, false)) return div end

function h.allToggleClass(n, isAttr) local dot = isAttr and '.' or '' return ('%sbracket-toggle-allrounds-%s'):format(dot, n) end

function h.roundToggleClass(n, i, isAttr) local dot = isAttr and '.' or '' return ('%sbracket-toggle-round-%s-%s'):format(dot, n, i) end

function h.makeTogglerButtons(togglers, n)	local tbl = {} tbl[1] = h.makeToggler(n, 1) for i, _ in ipairs(togglers) do		if i == #togglers then tbl[#tbl+1] = h.makeLastToggler(n) else -- first add 1 because we already did 1 from the default bracket tbl[#tbl+1] = h.makeToggler(n, i + 1) end end return tbl end

function h.makeToggler(n, i)	local div = mw.html.create('div') :addClass('bracket-toggler') :wikitext('[') div:tag('span') :addClass('alwaysactive-toggler') :attr('data-toggler-hide', h.allToggleClass(n, true)) :attr('data-toggler-show', h.roundToggleClass(n, i + 1, true)) :wikitext('x') div:wikitext(']') return div end

function h.makeLastToggler(n) local div = mw.html.create('div') :addClass('bracket-toggler') div:tag('span') :addClass('alwaysactive-toggler') :attr('data-toggler-hide', h.allToggleClass(n, true)) :attr('data-toggler-show', h.roundToggleClass(n, 1, true)) :wikitext('<<') return div end

function h.setTableHidden(tableList, initround) initround = tonumber(initround or 1) or 1 for k, tbl in ipairs(tableList) do		if k ~= initround then tbl:addClass('toggle-section-hidden') end end end

function h.printBracket(args, settings, tbl, togglers) tbl:addClass('bracket-grid') :css({			['grid-template-columns'] = h.getGTC(settings, args),			['grid-template-rows'] = h.getGTR(settings, args.notitle)		}) for round, col in ipairs(settings) do		h.addLinesColumn(tbl, col.lines, round, not args.notitle) h.addMatchesColumn(tbl, args, col.matches, round, not args.notitle, togglers[round]) end return tbl end

function h.getGTC(settings, args) local scores = {} for round, col in ipairs(settings) do		scores[round] = args[round] and tonumber(args[round].extendedseries or '') or col.extendedseries or 1 end local firstcol = settings[1].lines and next(settings[1].lines) local firstwidth = firstcol and LINEWIDTH or '0' return h.getCustomGTC(scores, args.roundwidth, args.roundminwidth, firstwidth) end

function h.getCustomGTC(scores, roundwidth, minwidth, firstwidth) local linewidth = minwidth and ' minmax(2em,3em) ' or ' 3em ' roundwidth = h.getRoundwidth(roundwidth) minwidth = h.parseWidth(minwidth) or roundwidth local widths = {} for k, v in ipairs(scores) do		local min = (SCOREWIDTH * (v - 1) + minwidth) local max = (SCOREWIDTH * (v - 1) + roundwidth) widths[#widths+1] = ('minmax(%sem, %sem)'):format(min, max) end return firstwidth .. ' ' .. table.concat(widths, linewidth) end

function h.getRoundwidth(roundwidth) if roundwidth then return h.parseWidth(roundwidth) else return ROUNDWIDTH end end

function h.parseWidth(width) if not width then return nil end return tonumber(width:gsub('em',) or ) end

function h.getGTR(settings, notitle) local max = 0 for _, col in ipairs(settings) do		local total = 0 for _, match in ipairs(col.matches) do			total = total + (match.above or 0) if match.display == 'match' then total = total + ROWS_PER_TEAM elseif match.display == 'hline' then total = total + ROWS_PER_HLINE end end if total > max then max = total end end if not notitle then max = max + ROWS_PER_TITLE end return ('repeat(%s,var(--grid-row-height))'):format(max) end

function h.addLinesColumn(tbl, lineData, r, addtitle) local roundname = 'round' .. (r - 1) if not lineData then return end for m, row in ipairs(lineData) do		if m == 1 and addtitle then h.addBracketLine(tbl, roundname, row, 2) else h.addBracketLine(tbl, roundname, row, 0) end end return end

function h.addBracketLine(tbl, roundname, linerow, extra) if linerow.above + extra > 0 then tbl:tag('div') :addClass('bracket-line') :addClass(roundname) :cssText(('grid-row:span %s;'):format(linerow.above + extra)) end tbl:tag('div') :addClass('bracket-line') :addClass(linerow.class) :addClass(roundname) :cssText(('grid-row:span %s;'):format(linerow.height)) return end

function h.addMatchesColumn(tbl, args, data, r, addtitle, toggler) local roundname = 'round' .. r	if addtitle then local title = args[r] and args[r].title or data.title or '' h.makeTitle(tbl, roundname, title, toggler) end for m, row in ipairs(data) do		local game = args[r] and args[r][m] or { team1 = {}, team2 = {} } if row.above then h.addSpacer(tbl, roundname, row.above) end if row.display == 'match' then h.makeMatch(tbl, game, roundname, not args.nolabels and row.label, args.teamstyle) elseif row.display == 'hline' then h.makeHorizontalCell(tbl, roundname) end end return end

function h.makeTitle(tbl, roundname, text, toggler) local outerdiv = tbl:tag('div') :addClass('bracket-grid-header') :addClass(roundname) local innerdiv = outerdiv:tag('div') :addClass('bracket-header-content') :wikitext(text) if toggler then innerdiv:node(toggler) end end

function h.makeHorizontalCell(tbl, roundname) tbl:tag('div') :addClass('bracket-spacer') :addClass('horizontal') :addClass(roundname) return end

function h.makeMatch(tbl, game, roundname, label, teamstyle) if game.label then label = game.label end h.addSpacer(tbl, roundname, nil, label) h.makeTeam(tbl, roundname, game, game.team1, '1', teamstyle) h.makeTeam(tbl, roundname, game, game.team2, '2', teamstyle) h.addSpacer(tbl, roundname) return end

function h.addSpacer(tbl, roundname, n, label) local div = tbl:tag('div') :addClass('bracket-spacer') :addClass(roundname) if label then div:wikitext(label) end if n then div:cssText(('grid-row:span %s;'):format(n)) end return end

function h.makeTeam(tbl, roundname, game, data, n, teamstyle) local isWinner = game.winner == n	local isbye = util_args.castAsBool(data.bye) local line = tbl:tag('div') :addClass('bracket-team') :addClass(roundname) :addClass(game.class) util_esports.addTeamHighlighter(line, data.teamfinal or data.playerlink or data.player or data.team) if isWinner then line:addClass('bracket-winner') end local team = line:tag('div') :addClass('bracket-team-name') if data.free then team:wikitext(data.free) elseif isbye then team:wikitext('BYE') line:addClass('bracket-bye') else bracket_wiki.teamDisplay(team, data, teamstyle) end h.makeScore(line, data.score, isbye, game.winners, n) end

function h.makeScore(line, score, isbye, winners, n)	local tbl = util_text.split(tostring(score or ''),sep) tbl_win = winners and util_text.split(winners, sep) or {} for k, v in ipairs(tbl) do		local div = line:tag('div') :addClass('bracket-team-points') :wikitext(v or (isbye and '-') or '') if tbl_win[k] == n then div:addClass('bracket-score-winner') elseif tbl_win[k] then div:addClass('bracket-score-loser') end end end

local p = {}

function p.main(frame) local tpl_args = util_args.merge(true) -- use require instead of loadData so that we can use next and # local settings local function assignBracket settings = require('Module:Bracket/'.. tpl_args[1]) end if not tpl_args[1] then error('No bracket definition provided!') elseif pcall(assignBracket) then -- pass else error(('Bracket %s is not a valid input'):format(tpl_args[1])) end local args = h.processArgs(tpl_args) h.processSettings(settings, args) if util_args.castAsBool(args.cargo) then h.addCargoData(args, settings) end return h.makeOutput(args, settings) end

return p