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

local Tabs = {}

--[[
=================================================================
Start of dependency: Module:Table
=================================================================
]]
local Table = {}

function Table.size(tbl)
	local i = 0
	for _ in pairs(tbl) do
		i = i + 1
	end
	return i
end

function Table.includes(tbl, value, isPattern)
	for _, entry in pairs(tbl) do
		if isPattern and string.find(entry, value)
			or not isPattern and entry == value then
				return true
		end
	end
	return false
end

function Table.getKeyOfValue(tbl, value)
	for key, entry in pairs(tbl) do
		if entry == value then
			return key
		end
	end
	return nil
end

function Table.filter(tbl, predicate, argument)
	local filteredTbl = {}
	local foundMatches = 1

	for _, entry in pairs(tbl) do
		if predicate(entry, argument) then
			filteredTbl[foundMatches] = entry
			foundMatches = foundMatches + 1
		end
	end

	return filteredTbl
end

function Table.filterByKey(tbl, predicate)
	local filteredTbl = {}

	for key, entry in pairs(tbl) do
		if predicate(key, entry) then
			filteredTbl[key] = entry
		end
	end

	return filteredTbl
end

function Table.isEmpty(tbl)
	if tbl == nil then
		return true
	end
	for _, _ in pairs(tbl) do
		return false
	end
	return true
end

function Table.isNotEmpty(tbl)
	return not Table.isEmpty(tbl)
end

function Table.copy(tbl)
	local result = {}
	for key, entry in pairs(tbl) do
		result[key] = entry
	end
	return result
end

function Table.deepCopy(tbl_, options)
	options = options or {}
	assert(type(tbl_) == 'table', 'Table.deepCopy: Input must be a table')

	local function deepCopy(tbl)
		local result = {}
		for key, value in pairs(tbl) do
			result[key] = type(value) == 'table' and deepCopy(value) or value
		end
		if options.copyMetatable then
			local metatable = getmetatable(tbl)
			if type(metatable) == 'table' then
				setmetatable(result, deepCopy(metatable))
			end
		end
		return result
	end

	return deepCopy(tbl_)
end

function Table.deepEquals(xTable, yTable)
	assert(type(xTable) == 'table', 'Table.deepEquals: First argument must be a table')
	assert(type(yTable) == 'table', 'Table.deepEquals: Second argument must be a table')

	for key, value in pairs(xTable) do
		if not Logic.deepEquals(value, yTable[key]) then
			return false
		end
	end

	for key, _ in pairs(yTable) do
		if xTable[key] == nil then
			return false
		end
	end

	return true
end

function Table.mergeInto(target, ...)
	local objs = Table.pack(...)
	for i = 1, objs.n do
		if type(objs[i]) == 'table' then
			for key, value in pairs(objs[i]) do
				target[key] = value
			end
		end
	end
	return target
end

function Table.merge(...)
	return Table.mergeInto({}, ...)
end

function Table.deepMergeInto(target, ...)
	local tbls = Table.pack(...)
	for i = 1, tbls.n do
		if type(tbls[i]) == 'table' then
			for key, value in pairs(tbls[i]) do
				if type(target[key]) == 'table' and type(value) == 'table' then
					Table.deepMergeInto(target[key], value)
				else
					target[key] = value
				end
			end
		end
	end
	return target
end

function Table.deepMerge(...)
	return Table.deepMergeInto({}, ...)
end

function Table.map(xTable, f)
	local yTable = {}
	for xKey, xValue in pairs(xTable) do
		local yKey, yValue = f(xKey, xValue)
		yTable[yKey] = yValue
	end
	return yTable
end

function Table.mapArgumentsByPrefix(args, prefixes, f, noInterleave)
	local function indexFromKey(key)
		local prefix, index = key:match('^([%a_]+)(%d+)$')
		if Table.includes(prefixes, prefix) then
			return tonumber(index), prefix
		else
			return nil
		end
	end

	return Table.mapArguments(args, indexFromKey, f, noInterleave)
end

function Table.mapArguments(args, indexFromKey, f, noInterleave)
	local entriesByIndex = {}

	-- Non-numeric args
	for key, _ in pairs(args) do
		local function post(index, ...)
			if index and not entriesByIndex[index] then
				entriesByIndex[index] = f(key, index, ...)
			end
		end
		if type(key) == 'string' then
			post(indexFromKey(key))
		end
	end

	if noInterleave then
		return entriesByIndex
	end

	-- Numeric index entries fills in gaps of prefixN= entries if not disabled
	local entryIndex = 1
	for argIndex = 1, math.huge do
		if not args[argIndex] then
			break
		end
		while entriesByIndex[entryIndex] do
			entryIndex = entryIndex + 1
		end
		entriesByIndex[entryIndex] = f(argIndex, entryIndex)
	end

	return entriesByIndex
end

function Table.mapValues(xTable, f)
	local yTable = {}
	for xKey, xValue in pairs(xTable) do
		yTable[xKey] = f(xValue)
	end
	return yTable
end

function Table.all(tbl, predicate)
	for key, value in pairs(tbl) do
		if not predicate(key, value) then
			return false
		end
	end
	return true
end

function Table.any(tbl, predicate)
	for key, value in pairs(tbl) do
		if predicate(key, value) then
			return true
		end
	end
	return false
end

function Table.groupBy(tbl, f)
	local groups = {}
	for key, value in pairs(tbl) do
		local groupKey = f(key, value)
		if not groups[groupKey] then
			groups[groupKey] = {}
		end
		groups[groupKey][key] = value
	end
	return groups
end

function Table.extract(tbl, key)
	local value = tbl[key]
	tbl[key] = nil
	return value
end

function Table.getByPathOrNil(tbl, path)
	for _, fieldName in ipairs(path) do
		if type(tbl) ~= 'table' then
			return nil
		end
		tbl = tbl[fieldName]
	end
	return tbl
end

function Table.setByPath(tbl, path, value)
	for i = 1, #path - 1 do
		if tbl[path[i]] == nil then
			tbl[path[i]] = {}
		end
		tbl = tbl[path[i]]
	end
	tbl[path[#path]] = value
end

function Table.uniqueKey(tbl)
	local key0 = nil
	for key, _ in pairs(tbl) do
		if key0 ~= nil then return nil end
		key0 = key
	end
	return key0
end

function Table.entries(tbl)
	local entries = {}
	for key, value in pairs(tbl) do
		table.insert(entries, {key, value})
	end
	return entries
end

function Table.pack(...)
	return {n = select('#', ...), ...}
end

Table.iter = {}

function Table.iter.spairs(tbl, order)
	local keys = {}
	for k in pairs(tbl) do keys[#keys+1] = k end

	if order then
		table.sort(keys, function(a,b) return order(tbl, a, b) end)
	else
		table.sort(keys)
	end

	local i = 0
	return function()
		i = i + 1
		if keys[i] then
			return keys[i], tbl[keys[i]]
		end
	end
end

function Table.iter.pairsByPrefix(tbl, prefixes, options)
	options = options or {}
	if type(prefixes) == 'string' then
		prefixes = {prefixes}
	end

	local getByPrefixes = function(index)
		for _, prefix in ipairs(prefixes) do
			local key = prefix .. index
			if tbl[key] then
				return key, tbl[key]
			end
		end
	end

	local i = 1
	return function()
		local key, value = getByPrefixes(i)
		if options.requireIndex == false and i == 1 and not value then
			key, value = getByPrefixes('')
		end
		i = i + 1
		if value then
			return key, value, (i - 1)
		else
			return nil
		end
	end
end

--[[
=================================================================
Start of dependency: Module:Logic
=================================================================
]]
local Logic = {}

function Logic.emptyOr(val1, val2, default)
	if not Logic.isEmpty(val1) then
		return val1
	elseif not Logic.isEmpty(val2) then
		return val2
	else
		return default
	end
end

function Logic.nilOr(...)
	local args = Table.pack(...)
	for i = 1, args.n do
		local arg = args[i]
		local val
		if type(arg) == 'function' then
			val = arg()
		else
			val = arg
		end
		if val ~= nil then
			return val
		end
	end
	return nil
end

function Logic.isEmpty(val)
	if type(val) == 'table' then
		return Table.isEmpty(val)
	else
		return val == '' or val == nil
	end
end

function Logic.isNotEmpty(val)
	if type(val) == 'table' then
		return Table.isNotEmpty(val)
	else
		return val ~= nil and val ~= ''
	end
end

function Logic.nilIfEmpty(val)
	return Logic.isNotEmpty(val) and val or nil
end

function Logic.isDeepEmpty(val)
	return Logic.isEmpty(val) or type(val) == 'table' and
		Table.all(val, function(key, item) return Logic.isDeepEmpty(item) end)
end

function Logic.isNotDeepEmpty(val)
	return not Logic.isDeepEmpty(val)
end

function Logic.readBool(val)
	return val == 'true' or val == 't' or val == 'yes' or val == 'y' or val == true or val == '1' or val == 1
end

function Logic.readBoolOrNil(val)
	if Logic.readBool(val) then
		return true
	elseif val == 'false' or val == 'f' or val == 'no' or val == 'n' or val == false or val == '0' or val == 0 then
		return false
	else
		return nil
	end
end

function Logic.nilThrows(val)
	if val == nil then
		error('Unexpected nil', 2)
	end
	return val
end

function Logic.tryCatch(try, catch)
	local ran, result = pcall(try)
	if not ran then
		catch(result)
	else
		return result
	end
end

function Logic.deepEquals(x, y)
	if x == y then
		return true
	elseif type(x) == 'table' and type(y) == 'table' then
		return Table.deepEquals(x, y)
	else
		return false
	end
end

--[[
=================================================================
Start of dependency: Module:Array
=================================================================
]]
local Array = {}

function Array.randomize(tbl)
	math.randomseed(os.time())
	for i = #tbl, 2, -1 do
		local j = math.random(i)
		tbl[i], tbl[j] = tbl[j], tbl[i]
	end
	return tbl
end

function Array.isArray(tbl)
	return type(tbl) == 'table' and Table.size(tbl) == #tbl
end

function Array.copy(tbl)
	local copy = {}
	for _, element in ipairs(tbl) do
		table.insert(copy, element)
	end
	return copy
end

function Array.sub(tbl, startIndex, endIndex)
	if startIndex < 0 then startIndex = #tbl + 1 + startIndex end
	if not endIndex then endIndex = #tbl end
	if endIndex < 0 then endIndex = #tbl + 1 + endIndex end

	local subArray = {}
	for index = startIndex, endIndex do
		table.insert(subArray, tbl[index])
	end
	return subArray
end

function Array.map(elements, funct)
	local mappedArray = {}
	for index, element in ipairs(elements) do
		local mappedElement = funct(element, index)
		table.insert(mappedArray, mappedElement)
	end
	return mappedArray
end

function Array.filter(tbl, predicate)
	local filteredArray = {}
	for index, element in ipairs(tbl) do
		if predicate(element, index) then
			table.insert(filteredArray, element)
		end
	end
	return filteredArray
end

function Array.flatten(tbl)
	local flattenedArray = {}
	for _, x in ipairs(tbl) do
		if type(x) == 'table' then
			for _, y in ipairs(x) do
				table.insert(flattenedArray, y)
			end
		else
			table.insert(flattenedArray, x)
		end
	end
	return flattenedArray
end

function Array.flatMap(elements, funct)
	return Array.flatten(Array.map(elements, funct))
end

function Array.all(tbl, predicate)
	for _, element in ipairs(tbl) do
		if not predicate(element) then
			return false
		end
	end
	return true
end

function Array.any(tbl, predicate)
	for _, element in ipairs(tbl) do
		if predicate(element) then
			return true
		end
	end
	return false
end

function Array.find(tbl, predicate)
	for index, element in ipairs(tbl) do
		if predicate(element, index) then
			return element
		end
	end
	return nil
end

function Array.groupBy(tbl, funct)
	local groupsByKey = {}
	local groups = {}
	for _, xValue in ipairs(tbl) do
		local yValue = funct(xValue)
		if yValue then
			local group = groupsByKey[yValue]
			if not group then
				group = {}
				groupsByKey[yValue] = group
				table.insert(groups, group)
			end
			table.insert(group, xValue)
		end
	end
	return groups, groupsByKey
end

function Array.groupAdjacentBy(array, f, equals)
	equals = equals or Logic.deepEquals
	local groups = {}
	local currentKey
	for index, elem in ipairs(array) do
		local key = f(elem)
		if index == 1 or not equals(key, currentKey) then
			currentKey = key
			table.insert(groups, {})
		end
		table.insert(groups[#groups], elem)
	end
	return groups
end

function Array.lexicalCompare(tblX, tblY)
	for index = 1, math.min(#tblX, #tblY) do
		if tblX[index] < tblY[index] then
			return true
		elseif tblX[index] > tblY[index] then
			return false
		end
	end
	return #tblX < #tblY
end

function Array.lexicalCompareIfTable(y1, y2)
	if type(y1) == 'table' and type(y2) == 'table' then
		return Array.lexicalCompare(y1, y2)
	else
		return y1 < y2
	end
end

function Array.sortBy(tbl, funct, compare)
	local copy = Table.copy(tbl)
	Array.sortInPlaceBy(copy, funct, compare)
	return copy
end

function Array.sortInPlaceBy(tbl, funct, compare)
	compare = compare or Array.lexicalCompareIfTable
	table.sort(tbl, function(x1, x2) return compare(funct(x1), funct(x2)) end)
end

function Array.reverse(tbl)
	local reversedArray = {}
	for index = #tbl, 1, -1 do
		table.insert(reversedArray, tbl[index])
	end
	return reversedArray
end

function Array.append(tbl, ...)
	return Array.appendWith(Array.copy(tbl), ...)
end

function Array.appendWith(tbl, ...)
	local elements = Table.pack(...)
	for index = 1, elements.n do
		if elements[index] ~= nil then
			table.insert(tbl, elements[index])
		end
	end
	return tbl
end

function Array.extend(tbl, ...)
	return Array.extendWith({}, tbl, ...)
end

function Array.extendWith(tbl, ...)
	local arrays = Table.pack(...)
	for index = 1, arrays.n do
		if type(arrays[index]) == 'table' then
			for _, element in ipairs(arrays[index]) do
				table.insert(tbl, element)
			end
		elseif arrays[index] ~= nil then
			table.insert(tbl, arrays[index])
		end
	end
	return tbl
end

function Array.mapIndexes(funct)
	local arr = {}
	for index = 1, math.huge do
		local y = funct(index)
		if y then
			table.insert(arr, y)
		else
			break
		end
	end
	return arr
end

function Array.range(from, to)
	local elements = {}
	for element = from, to do
		table.insert(elements, element)
	end
	return elements
end

function Array.extractKeys(tbl, iterator, ...)
	iterator = iterator or pairs
	local array = {}
	for key, _ in iterator(tbl, ...) do
		table.insert(array, key)
	end
	return array
end

function Array.extractValues(tbl, iterator, ...)
	iterator = iterator or pairs
	local array = {}
	for _, item in iterator(tbl, ...) do
		table.insert(array, item)
	end
	return array
end

function Array.forEach(elements, funct)
	for index, element in ipairs(elements) do
		funct(element, index)
	end
end

function Array.reduce(array, operator, initialValue)
	local aggregate
	if initialValue ~= nil then
		aggregate = initialValue
	else
		aggregate = array[1]
	end

	for index = initialValue ~= nil and 1 or 2, #array do
		aggregate = operator(aggregate, array[index])
	end
	return aggregate
end

function Array.maxBy(array, funct, compare)
	compare = compare or Array.lexicalCompareIfTable
	local max, maxScore
	for _, item in ipairs(array) do
		local score = funct(item)
		if max == nil or compare(maxScore, score) then
			max = item
			maxScore = score
		end
	end
	return max
end

function Array.max(array, compare)
	return Array.maxBy(array, function(x) return x end, compare)
end

function Array.minBy(array, funct, compare)
	compare = compare or Array.lexicalCompareIfTable
	local min, minScore
	for _, item in ipairs(array) do
		local score = funct(item)
		if min == nil or compare(score, minScore) then
			min = item
			minScore = score
		end
	end
	return min
end

function Array.min(array, compare)
	return Array.minBy(array, function(x) return x end, compare)
end

function Array.indexOf(array, pred)
	for ix, elem in ipairs(array) do
		if pred(elem, ix) then
			return ix
		end
	end
	return 0
end

function Array.unique(elements)
	local elementCache = {}
	local uniqueElements = {}
	for _, element in ipairs(elements) do
		if elementCache[element] == nil then
			table.insert(uniqueElements, element)
			elementCache[element] = true
		end
	end
	return uniqueElements
end

function Array.parseCommaSeparatedString(inputString, sep)
	if Logic.isEmpty(inputString) then return {} end
	return Array.map(mw.text.split(inputString, sep or ','), mw.text.trim)
end

function Array.interleave(elements, x)
	local size = #elements
	return Array.flatMap(elements, function(element, index)
		if index == size then
			return {element}
		end
		return {element, x}
	end)
end

--[[
=================================================================
Start of dependency: Module:Operator
=================================================================
]]
local Operator = {}

function Operator.add(a, b) return a + b end
function Operator.sub(a, b) return a - b end
function Operator.mul(a, b) return a * b end
function Operator.div(a, b) return a / b end
function Operator.pow(a, b) return a ^ b end
function Operator.eq(a, b) return a == b end
function Operator.neq(a, b) return a ~= b end
function Operator.lt(a, b) return a < b end
function Operator.le(a, b) return a <= b end
function Operator.gt(a, b) return a > b end
function Operator.ge(a, b) return a >= b end

function Operator.property(item)
	assert(type(item) == 'string' or type(item) == 'number', 'Invalid or missing input to `Operator.property`')
	local pathSegments = mw.text.split(item, '.', true)
	return function(tbl)
		local selected = tbl
		for segmentIndex, pathSegment in ipairs(pathSegments) do
			if type(selected) ~= 'table' and segmentIndex == 1 then
				error('Nil supplied to `Operator.property(' .. item .. ')`')
			elseif type(selected) ~= 'table' then
				local pathUntilHere = Array.sub(pathSegments, 1, segmentIndex - 1)
				error('Could not index "tbl.' .. table.concat(pathUntilHere, '.') .. '"')
			end
			selected = selected[pathSegment] or selected[tonumber(pathSegment)]
		end
		return selected
	end
end

function Operator.method(funcName, ...)
	local args = {...}
	return function(obj)
		return obj[funcName](obj, unpack(args))
	end
end

--[[
=================================================================
Start of dependency: Module:Page
=================================================================
]]
local Page = {}

function Page.exists(link)
	local existingPage = mw.title.new(link)
	if existingPage == nil then
		return false
	end
	return existingPage.exists
end

function Page.makeInternalLink(options, display, customLink)
	if type(options) == 'string' then
		customLink = display
		display = options
	end
	if Logic.isEmpty(display) then
		return nil
	elseif Logic.isEmpty(customLink) then
		customLink = display
	end

	if (options or {}).onlyIfExists == true and (not Page.exists(customLink)) then
		return nil
	end

	return '[[' .. customLink .. '|' .. display .. ']]'
end

function Page.makeExternalLink(display, link)
	if Logic.isEmpty(display) or Logic.isEmpty(link) then
		return nil
	end
	return '[' .. link .. ' ' .. display .. ']'
end

function Page.pageifyLink(link)
	if Logic.isEmpty(link) then
		return nil
	end
	return (mw.ext.TeamLiquidIntegration.resolve_redirect(link):gsub(' ', '_'))
end

--[[
=================================================================
Start of original Module:Tabs code
=================================================================
]]
function Tabs._readArguments(args, options)
	local tabArgs = {}
	local tabIndex = 1
	local this = tonumber(args.This)
	local this2 = tonumber(args.This2)

	while args['name' .. tabIndex] or args['link' .. tabIndex] do
		if args['content' .. tabIndex] or not options.removeEmptyTabs then
			table.insert(tabArgs, {
				name = Table.extract(args, 'name' .. tabIndex),
				link = Table.extract(args, 'link' .. tabIndex),
				content = Table.extract(args, 'content' .. tabIndex),
				tabs = Table.extract(args, 'tabs' .. tabIndex),
				this = this == tabIndex or (options.allowThis2 and this2 == tabIndex),
			})
		end
		tabIndex = tabIndex + 1
	end

	if Logic.readBool(args.returnIfEmpty) then
		return tabArgs
	end

	assert(Logic.isNotEmpty(tabArgs), 'You are trying to add a "Tabs" template without arguments for names nor links')

	return tabArgs
end

function Tabs._setThis(tabArgs)
	if Array.any(tabArgs, Operator.property('this')) then return end

	local fullPageName = mw.title.getCurrentTitle().prefixedText
	local this
	local maxLinkLength = -1

	Array.forEach(tabArgs, function (tab, tabIndex)
		local link = tab.link
		if not link then return end
		link = link:gsub('_', ' ')
		local linkLength = string.len(link)
		local charAfter = string.sub(fullPageName, linkLength + 1, linkLength + 1)
		local pagePartial = string.sub(fullPageName, 1, linkLength)
		if pagePartial == link and (charAfter == '/' or charAfter == '') and linkLength > maxLinkLength then
			maxLinkLength = linkLength
			this = tabIndex
		end
	end)

	if not this then return end
	tabArgs[this].this = true
end

function Tabs._buildContentDiv(hasContent, hybridTabs, noPadding)
	if hasContent then
		local contentDiv = mw.html.create('div')
			:addClass('tabs-content')
		if hybridTabs then
			contentDiv
				:css('border-style', 'none !important')
				:css('padding', '0 !important')
		elseif noPadding then
			contentDiv
				:css('padding', '0 !important')
		end
		return contentDiv
	end

	local style = ''
	if hybridTabs then
		style = 'border-style:none !important; padding:0 !important;'
	elseif noPadding then
		style = 'padding:0 !important;'
	end
	return '\n<div class="tabs-content" style="' .. style .. '">'
end

function Tabs._single(tab, showHeader)
	local header
	if showHeader then
		header = mw.html.create()
			:tag('h6'):wikitext(tab.name):done()
			:newline()
	end
	return mw.html.create()
		:node(header)
		:node(tab.content)
end

function Tabs._getDisplayNameFromLink(link)
	local linkParts = mw.text.split(link, '/', true)
	return linkParts[#linkParts]
end

function Tabs.static(args)
	args = args or {}

	local tabArgs = Tabs._readArguments(args, {allowThis2 = true})
	local tabCount = #tabArgs
	if tabCount == 0 then return end

	Tabs._setThis(tabArgs)

	local tabs = mw.html.create('ul')
		:attr('class', 'nav nav-tabs navigation-not-searchable tabs tabs' .. tabCount)
		:attr('data-nosnippet')

	local subTabs = mw.html.create()

	Array.forEach(tabArgs, function(tab)
		local name = tab.name or Tabs._getDisplayNameFromLink(tab.link)
		local text = tab.link and Page.makeInternalLink({}, name, tab.link) or tab.name
		tabs:tag('li'):addClass(tab.this and 'active' or nil):wikitext(text)
		subTabs:node(tab.this and tab.tabs or nil)
	end)

	return mw.html.create()
		:tag('div')
			:addClass('tabs-static')
			:attr('data-nosnippet', '')
			:node(tabs)
			:done()
		:node(subTabs)
end

function Tabs.dynamic(args)
	args = args or {}

	local tabArgs = Tabs._readArguments(args, {removeEmptyTabs = Logic.readBool(args.removeEmptyTabs)})
	local tabCount = #tabArgs
	if tabCount == 0 then return end

	local hasContent = Array.all(tabArgs, function(tab)
		return Logic.isNotEmpty(tab.content) end)
	local allEmpty = Array.all(tabArgs, function(tab)
		return Logic.isEmpty(tab.content) end)
	assert(hasContent or allEmpty, 'Some of the tabs have contents while others do not')

	local isSingular = tabCount == 1 and hasContent
	if isSingular and not Logic.readBool(args.showSingularAsTab) then
		return Tabs._single(tabArgs[1], not Logic.readBool(args.suppressHeader))
	end

	local tabs = mw.html.create('ul')
		:addClass('nav nav-tabs tabs tabs' .. tabCount)

	if not Array.any(tabArgs, Operator.property('this')) then
		tabArgs[1].this = true
	end

	local build = function(obj, elementType, content, class, isActive)
		local element = mw.html.create(elementType)
			:addClass(class)
			:addClass(isActive and 'active' or nil)
			:newline()
			:node(content)

		obj:newline():node(element)
	end

	Array.forEach(tabArgs, function(tabData, tabIndex)
		build(tabs, 'li', tabData.name, 'tab' .. tabIndex, tabData.this)
	end)

	if not Logic.nilOr(Logic.readBoolOrNil(args['hide-showall']), isSingular) then
		tabs:tag('li')
			:addClass('show-all')
			:wikitext('Show All')
	end

	tabs:newline()

	local contents = Tabs._buildContentDiv(
		hasContent,
		Logic.readBool(args['hybrid-tabs']),
		Logic.readBool(args['no-padding'])
	)

	if not hasContent then
		return '<div class="tabs-dynamic navigation-not-searchable" data-nosnippet>\n'
			.. tostring(tabs) .. contents
	end

	Array.forEach(tabArgs, function(tabData, tabIndex)
		build(contents, 'div', tabData.content, 'content' .. tabIndex, tabData.this)
	end)

	return mw.html.create('div')
		:addClass('tabs-dynamic navigation-not-searchable')
		:attr('data-nosnippet')
		:node(tabs)
		:newline()
		:node(contents)
end

return Tabs