Vainglory Esports Wiki
[checked revision][pending revision]
m (Syncing content across wikis, if something seems broken as a result let me know)
m (Syncing content across wikis, if something seems broken as a result let me know)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
local util_table = require('Module:TableUtil')
 
 
local util_args = require('Module:ArgsUtil')
 
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 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 = {}
 
local h = {}
Line 9: Line 25:
 
local args = {}
 
local args = {}
 
for k, v in pairs(tpl_args) do
 
for k, v in pairs(tpl_args) do
if k:find('R%d+M%d+_.*_') then
+
if type(k) ~= 'string' then
  +
-- pass
local match, team, val = k:match('(R%d+M%d+)_(.*)_(.*)')
 
if not args[match] then
+
elseif k:find('R%d+M%d+_.*_') then
  +
local r, m, val, team = k:match('R(%d+)M(%d+)_(.*)_(%d+)')
args[match] = { team1 = {}, team2 = {} }
 
  +
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
 
end
args[match][team][val] = v
 
 
elseif k:find('R%d+M%d+_.*') then
 
elseif k:find('R%d+M%d+_.*') then
local match, val = k:match('(R%d+M%d+)_(.*)')
+
local r, m, val = k:match('R(%d+)M(%d+)_(.*)')
  +
r = tonumber(r)
if not args[match] then
 
  +
m = tonumber(m)
args[match] = { team1 = {}, team2 = {} }
 
  +
h.initializeMatch(args, r, m)
end
 
args[match][val] = v
+
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
 
else
 
args[k] = v
 
args[k] = v
Line 28: Line 55:
 
end
 
end
   
function h.printBracket(args, settings)
+
function h.initializeMatch(args, r, m)
  +
if not args[r] then
local tbl = mw.html.create('div')
 
  +
args[r] = {}
:addClass('bracket-grid')
 
  +
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({
 
:css({
['grid-template-columns'] = h.gtcSetting(settings, args.roundwidth)
+
['grid-template-columns'] = h.getGTC(settings, args),
  +
['grid-template-rows'] = h.getGTR(settings, args.notitle)
 
})
 
})
 
for round, col in ipairs(settings) do
 
for round, col in ipairs(settings) do
 
h.addLinesColumn(tbl, col.lines, round, not args.notitle)
 
h.addLinesColumn(tbl, col.lines, round, not args.notitle)
h.addMatchesColumn(tbl, args, col.matches, round, not args.notitle)
+
h.addMatchesColumn(tbl, args, col.matches, round, not args.notitle, togglers[round])
 
end
 
end
 
return tbl
 
return tbl
 
end
 
end
   
function h.gtcSetting(settings, roundwidth)
+
function h.getGTC(settings, args)
local len = #settings
+
local scores = {}
local firstCol = settings[1].lines and next(settings[1].lines)
+
for round, col in ipairs(settings) do
  +
scores[round] = args[round] and tonumber(args[round].extendedseries or '') or col.extendedseries or 1
return ('%s repeat(%s, %s 3em)'):format(
 
  +
end
firstCol and '3em' or '0',
 
  +
local firstcol = settings[1].lines and next(settings[1].lines)
(len * 2 - 1),
 
  +
local firstwidth = firstcol and LINEWIDTH or '0'
roundwidth or '16em'
 
  +
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
 
end
   
Line 67: Line 377:
   
 
function h.addBracketLine(tbl, roundname, linerow, extra)
 
function h.addBracketLine(tbl, roundname, linerow, extra)
  +
if linerow.above + extra > 0 then
tbl:tag('div')
 
:addClass('bracket-line')
+
tbl:tag('div')
:addClass(roundname)
+
:addClass('bracket-line')
  +
:addClass(roundname)
:cssText(('grid-row:span %s;'):format(linerow.above + extra))
 
  +
:cssText(('grid-row:span %s;'):format(linerow.above + extra))
  +
end
 
tbl:tag('div')
 
tbl:tag('div')
 
:addClass('bracket-line')
 
:addClass('bracket-line')
Line 79: Line 391:
 
end
 
end
   
function h.addMatchesColumn(tbl, args, data, r, addtitle)
+
function h.addMatchesColumn(tbl, args, data, r, addtitle, toggler)
 
local roundname = 'round' .. r
 
local roundname = 'round' .. r
 
if addtitle then
 
if addtitle then
h.makeTitle(tbl, roundname, args['R' .. r .. '_title'] or data.title or '')
+
local title = args[r] and args[r].title or data.title or ''
  +
h.makeTitle(tbl, roundname, title, toggler)
 
end
 
end
 
for m, row in ipairs(data) do
 
for m, row in ipairs(data) do
local game = args[('R%sM%s'):format(r, m)] or { team1 = {}, team2 = {} }
+
local game = args[r] and args[r][m] or { team1 = {}, team2 = {} }
 
if row.above then
 
if row.above then
 
h.addSpacer(tbl, roundname, row.above)
 
h.addSpacer(tbl, roundname, row.above)
 
end
 
end
 
if row.display == 'match' then
 
if row.display == 'match' then
h.makeMatch(tbl, game, roundname, row.label)
+
h.makeMatch(tbl, game, roundname, not args.nolabels and row.label, args.teamstyle)
 
elseif row.display == 'hline' then
 
elseif row.display == 'hline' then
 
h.makeHorizontalCell(tbl, roundname)
 
h.makeHorizontalCell(tbl, roundname)
Line 98: Line 411:
 
end
 
end
   
function h.makeTitle(tbl, roundname, text)
+
function h.makeTitle(tbl, roundname, text, toggler)
tbl:tag('div')
+
local outerdiv = tbl:tag('div')
 
:addClass('bracket-grid-header')
 
:addClass('bracket-grid-header')
 
:addClass(roundname)
 
:addClass(roundname)
:tag('div')
+
local innerdiv = outerdiv:tag('div')
:addClass('bracket-header-content')
+
:addClass('bracket-header-content')
:wikitext(text)
+
:wikitext(text)
  +
if toggler then
return
 
  +
innerdiv:node(toggler)
  +
end
 
end
 
end
   
 
function h.makeHorizontalCell(tbl, roundname)
 
function h.makeHorizontalCell(tbl, roundname)
h.addSpacer(tbl, roundname, 2)
 
 
tbl:tag('div')
 
tbl:tag('div')
 
:addClass('bracket-spacer')
 
:addClass('bracket-spacer')
 
:addClass('horizontal')
 
:addClass('horizontal')
 
:addClass(roundname)
 
:addClass(roundname)
:cssText('grid-row:span 2;')
 
 
return
 
return
 
end
 
end
   
function h.makeMatch(tbl, game, roundname, label)
+
function h.makeMatch(tbl, game, roundname, label, teamstyle)
 
if game.label then label = game.label end
 
if game.label then label = game.label end
 
h.addSpacer(tbl, roundname, nil, label)
 
h.addSpacer(tbl, roundname, nil, label)
h.makeTeam(tbl, roundname, game.team1, game.winner == '1')
+
h.makeTeam(tbl, roundname, game, game.team1, '1', teamstyle)
h.makeTeam(tbl, roundname, game.team2, game.winner == '2')
+
h.makeTeam(tbl, roundname, game, game.team2, '2', teamstyle)
 
h.addSpacer(tbl, roundname)
 
h.addSpacer(tbl, roundname)
 
return
 
return
Line 131: Line 444:
 
:addClass('bracket-spacer')
 
:addClass('bracket-spacer')
 
:addClass(roundname)
 
:addClass(roundname)
:wikitext(label)
+
if label then
  +
div:wikitext(label)
  +
end
 
if n then
 
if n then
 
div:cssText(('grid-row:span %s;'):format(n))
 
div:cssText(('grid-row:span %s;'):format(n))
Line 138: Line 453:
 
end
 
end
   
function h.makeTeam(tbl, roundname, data, isWinner)
+
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')
 
local line = tbl:tag('div')
 
:addClass('bracket-team')
 
:addClass('bracket-team')
 
:addClass(roundname)
 
:addClass(roundname)
  +
:addClass(game.class)
  +
util_esports.addTeamHighlighter(line, data.teamfinal or data.playerlink or data.player or data.team)
 
if isWinner then
 
if isWinner then
 
line:addClass('bracket-winner')
 
line:addClass('bracket-winner')
Line 149: Line 468:
 
if data.free then
 
if data.free then
 
team:wikitext(data.free)
 
team:wikitext(data.free)
  +
elseif isbye then
  +
team:wikitext('BYE')
  +
line:addClass('bracket-bye')
 
else
 
else
bracket_wiki.teamDisplay(team, data)
+
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
line:tag('div')
 
:addClass('bracket-team-points')
 
:wikitext(data.score or '')
 
return
 
 
end
 
end
   
Line 163: Line 497:
 
local tpl_args = util_args.merge(true)
 
local tpl_args = util_args.merge(true)
 
-- use require instead of loadData so that we can use next() and #
 
-- use require instead of loadData so that we can use next() and #
local settings = require('Module:Bracket/'.. tpl_args.bracket)
+
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)
 
local args = h.processArgs(tpl_args)
  +
h.processSettings(settings, args)
return h.printBracket(args, settings)
 
  +
if util_args.castAsBool(args.cargo) then
  +
h.addCargoData(args, settings)
  +
end
  +
return h.makeOutput(args, settings)
 
end
 
end
   

Latest revision as of 22:39, 14 April 2019

To edit the documentation or categories for this module, click here.


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