-- Module to build tables for aggregated match results in sports
-- See documentation for details

local p = {}

-- Function to parse and expand a template with given parameters
local function expandTemplate(frame, templateName, params)
    return frame:expandTemplate{ title = templateName, args = params }
end

-- Function to check the existence of flagTemplate
local function templateExists(templateName)
    local title = mw.title.new('Template:' .. templateName)
    return title and title.exists
end

-- Function to process country codes and variants OR youth team flag templates and age level, dividing parameters by the "+" sign
local function processIcon(iconString)
    if not iconString or iconString:match("^%s*$") then
        return nil, nil  -- Return nil for both iconCode and variant if the input is empty or only whitespace
    elseif iconString:find('+') then
        local parts = mw.text.split(iconString, '+', true)
        local iconCode = parts[1]
        local variant = parts[2]
        return iconCode, variant
    else
        return iconString, nil  -- Return the input string as iconCode if no "+" is present
    end
end

-- Function to determine the correct ordinal suffix for a given number for the heading
local function ordinal(n)
    local last_digit = n % 10
    local last_two_digits = n % 100
    if last_digit == 1 and last_two_digits ~= 11 then
        return n .. 'st'
    elseif last_digit == 2 and last_two_digits ~= 12 then
        return n .. 'nd'
    elseif last_digit == 3 and last_two_digits ~= 13 then
        return n .. 'rd'
    else
        return n .. 'th'
    end
end

-- Function to replace wiki links with their display text or link text
local function replaceLink(match)
    local pipePos = match:find("|")
    if pipePos then
        return match:sub(pipePos + 1, -3) -- Return text after the '|'
    else
        return match:sub(3, -3) -- Return text without the brackets
    end
end

-- Function to clean and process the aggregate score for comparison
local function cleanScore(score)
    -- Return an empty string if score is nil or empty to avoid errors
    if not score or score:match("^%s*$") then
        return ''
    end

    -- Replace wiki links
    score = score:gsub("%[%[.-%]%]", replaceLink)

    -- Remove MediaWiki's unique placeholder sequences for references
    score = score:gsub('\127%\'"`UNIQ.-QINU`"%\'\127', '')

    -- Remove superscript tags and their contents
    score = score:gsub('<sup.->.-</sup>', '')

    -- Convert dashes to a standard format
    score = score:gsub('[–—―‒−]+', '-')

    -- Strip all characters except numbers, dashes and parentheses
    return score:gsub('[^0-9%-()]+', '')
end

-- Function to determine the winner based on scores within parentheses (first) or regular format (second)
local function determineWinner(cleanAggregate, matchType, team1, team2, boldWinner, colorWinner, aggregate, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals)
    local team1Winner, team2Winner = false, false
    local score1, score2
    local manualBold = false
    local manualColor = false
    local isDraw = false

    -- Handling for manual bolding
    if team1 and type(team1) == 'string' then
        manualBold1 = team1:find("'''") and not (team1:gsub("'''", ""):match("^%s*$"))
        team1 = team1:gsub("'''", "")
    end
    if team2 and type(team2) == 'string' then
        manualBold2 = team2:find("'''") and not (team2:gsub("'''", ""):match("^%s*$"))
        team2 = team2:gsub("'''", "")
    end

    if manualBold1 then
        team1Winner = true
        manualBold = true
    end
    if manualBold2 then
        team2Winner = true
        manualBold = true
    end

    -- Handling for manual coloring of team or aggregate cells
    if team1 and type(team1) == 'string' then
        manualColor1 = team1:find("''") and not (team1:gsub("''", ""):match("^%s*$"))
        team1 = team1:gsub("''", "")
    end
    if team2 and type(team2) == 'string' then
        manualColor2 = team2:find("''") and not (team2:gsub("''", ""):match("^%s*$"))
        team2 = team2:gsub("''", "")
    end
    if aggregate then
        if aggregate:find("'''") then
            aggregate = aggregate:gsub("'''", "")
            aggregate = "<strong>" .. aggregate .. "</strong>"
        end
        manualColorDraw = aggregate:find("''") and not (aggregate:gsub("''", ""):match("^%s*$"))
        aggregate = aggregate:gsub("''", "")
    end

    if manualColor1 then
        if not team1Winner then
            team1Winner = true
        end
        manualColor = true
    end
    if manualColor2 then
        if not team2Winner then
            team2Winner = true
        end
        manualColor = true
    end
    if manualColorDraw then
        isDraw = true
        manualColor = true
    end

    -- Regular winner determination logic if manual bolding or coloring is not conclusive
    if not team1Winner and not team2Winner and not isDraw and (boldWinner or colorWinner or isFBRStyle) then
        local parenthetical = cleanAggregate:match('%((%d+%-+%d+)%)')
        local outsideParenthetical = cleanAggregate:match('^(%d+%-+%d+)')
        if parenthetical then -- Prioritize checking score inside parenthetical
            score1, score2 = parenthetical:match('(%d+)%-+(%d+)')
        elseif outsideParenthetical then
            score1, score2 = outsideParenthetical:match('(%d+)%-+(%d+)')
        end

        if score1 and score2 then
            score1 = tonumber(score1)
            score2 = tonumber(score2)
        
            if score1 > score2 then
                team1Winner = true
            elseif score1 < score2 then
                team2Winner = true
            elseif score1 == score2 and legs == 2 and not disableAwayGoals then
                -- Apply away goals rule
                local cleanLeg1 = cleanScore(leg1Score):gsub('[()]', '')
                local cleanLeg2 = cleanScore(leg2Score):gsub('[()]', '')
                local _, team2AwayGoals = cleanLeg1:match('(%d+)%-+(%d+)')
                local team1AwayGoals = cleanLeg2:match('(%d+)%-+(%d+)')

                if team1AwayGoals and team2AwayGoals then
                    team1AwayGoals, team2AwayGoals = tonumber(team1AwayGoals), tonumber(team2AwayGoals)
            
                    if team1AwayGoals > team2AwayGoals then
                        team1Winner = true
                    elseif team2AwayGoals > team1AwayGoals then
                        team2Winner = true
                    end
                end
            end
    
            if (colorWinner or isFBRStyle) and legs == 0 then
                isDraw = not team1Winner and not team2Winner
            end
        end
    end

    return team1, team2, team1Winner, team2Winner, manualBold, manualColor, isDraw, aggregate
end

-- Function to check if any parameter in a given row is non-nil and non-empty
local function anyParameterPresent(startIndex, step, args)
    for index = startIndex, startIndex + step - 1 do
        if args[index] and args[index]:match("^%s*(.-)%s*$") ~= "" then
            return true
        end
    end
    return false
end

-- Function to add a legend to below the table when isFBRStyle is true
local function createFBRLegend()
    return mw.html.create('div')
        :css('font-size', '90%')
        :css('margin-bottom', '0.5em')
        :wikitext("Legend: Blue = home team win; Yellow = draw; Red = away team win.")
end

-- Function to check whether to reduce font size for upcoming matches
local function checkSmallText(str)
    -- Check for font size or small/big HTML tags
    if str:match("font%s?%-?size") or str:match("<small>") or str:match("<big>") then
        return false
    end

    -- Remove MediaWiki's unique placeholder sequences for references
    str = str:gsub('\127%\'"`UNIQ.-QINU`"%\'\127', '')

    -- Remove superscript tags and their contents
    str = str:gsub('<sup.->.-</sup>', '')

    -- Check for walkover-related strings (never shown in small text)
    if str:lower():match("walkover") or str:lower():match("w%.o%.") or str:lower():match("w/o") then
        return false
    end

    -- Replace wiki links with their display text or link text
    str = str:gsub("%[%[.-%]%]", replaceLink)

    -- Remove all text inside parentheses
    str = str:gsub("%b()", "")

    -- Exit if string contains only en/em dash
    if str == "—" or str == "–" then
        return false
    end

    -- Convert dashes to a standard format
    str = str:gsub('[–—―‒−]+', '-')

    -- Remove opening and closing HTML tags
    str = str:gsub("</?%w+[^>]*>", "")

    -- Remove all whitespace
    str = str:gsub("%s+", "")

    -- Check if the string matches only a scoreline
    if str:match("^%d+-%d+$") then
        return false
    else
        return true
    end
end

-- Function to format the dashes and winning notes for aggregate/leg score parameters
local function format_score(s, noWrap)
    if not s then return '' end -- Return empty string if input is nil

    local function format_dash(pattern)
        s = mw.ustring.gsub(s, '^' .. pattern, '%1–%2')
        s = mw.ustring.gsub(s, '%(' .. pattern, '(%1–%2')
    end

    -- Format dashes
    format_dash('%s*([%d%.]+)%s*[–—―‒−%-]%s*([%d%.]+)')
    format_dash('%s*([%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)')
    format_dash('%s*(%[%[[^%[%]]*%|[%d%.]+)%s*[–—―‒−%-]%s*([%d%.]+)')
    format_dash('%s*(%[[^%[%]%s]*%s+[%d%.]+)%s*[–—―‒−%-]%s*([%d%.]+)')
    format_dash('%s*(%[%[[^%[%]]*%|[%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)')
    format_dash('%s*(%[[^%[%]%s]*%s+[%d%.]+)%s*&[MmNn][Dd][Aa][Ss][Hh];%s*([%d%.]+)')

    -- Format winning notes in brackets
    if noWrap then
        s = mw.ustring.gsub(s, '(%(%d+%s*–%s*%d+)%s+[Pp]%.?[EeSs]?%.?[NnOo]?%.?%)', '%1 [[Penalty shoot-out (association football)|p]])')
        s = mw.ustring.gsub(s, '%([Aa]%.?[Ee]%.?[Tt]%.?%)', '([[Overtime (sports)#Association football|a.e.t.]])')
    else
        s = mw.ustring.gsub(s, '(%(%d+%s*–%s*%d+)%s+[Pp]%.?[EeSs]?%.?[NnOo]?%.?%)', '<span class="nowrap">%1 [[Penalty shoot-out (association football)|p]])</span>')
        s = mw.ustring.gsub(s, '%([Aa]%.?[Ee]%.?[Tt]%.?%)', '<span class="nowrap">([[Overtime (sports)#Association football|a.e.t.]])</span>')
    end
    s = mw.ustring.gsub(s, '%([Aa]%.?[Gg]?%.?[Rr]?%.?%)', '([[Away goals rule|a]])')

    return s
end

-- Function to rewrite anchor links of match scores
local function rewriteAnchorLinks(text, baselink)
    if not text then
        return text
    end
    return text:gsub('(%[%[)(#[^%[%]]*%|)', '%1' .. baselink .. '%2')
end

-- Main function that processes input and returns the wikitable
function p.main(frame)
    local args = require'Module:Arguments'.getArgs(frame, {trim = true})

    -- Check for section transclusion
    local tsection = frame:getParent().args['transcludesection'] or frame:getParent().args['section'] or ''
    local bsection = args['section'] or ''
    if tsection ~= '' and bsection ~= '' then
        if tsection ~= bsection then
            return ''  -- Return an empty string if sections don't match
        end
    end

    local root = mw.html.create()
    local matchType = (args.type == 'WNT' or args.type == 'MNT') and 'NT' or (args.type or 'club')  -- Set default match type to 'club'
    local isWNT = args.type == 'WNT'  -- Track if WNT was set
    local flagTemplate, flagParam1
    local noFlagIcons = false
    local fillBlanks = args.fill_blanks and (args.fill_blanks == 'y' or args.fill_blanks == 'yes' or args.fill_blanks == '1' or args.fill_blanks == 'true')
    local solidCell = args.solid_cell and (args.solid_cell == 'y' or args.solid_cell == 'yes' or args.solid_cell == '1' or args.solid_cell == 'true' or args.solid_cell == 'grey' or args.solid_cell == 'gray')
    local baselink = frame:getParent():getTitle()
    if mw.title.getCurrentTitle().text == baselink then	baselink = '' end

    -- Process the font size parameter
    local fontSize
    if args.font_size then
        -- Remove trailing '%' if present and convert to number
        fontSize = tonumber((args.font_size:gsub('%s*%%$', '')))
        if fontSize then
            fontSize = math.max(fontSize, 85)  -- Ensure font size is at least 85
        end
    end
    -- Calculate the font size for small text
    local smallFontSize
    if fontSize then
        smallFontSize = math.floor(((fontSize / 100) * 0.85) * 100)
    else
        smallFontSize = 85
    end

    -- Process flag parameter to determine flag template and variant
    if args.flag and args.flag:find('+') then
        flagTemplate, flagParam1 = processIcon(args.flag)  -- Process flag icons with variants
    else
        if args.flag then
            flagTemplate = args.flag
        elseif isWNT then
            flagTemplate = 'fbw'  -- Default to {{fbw}} for WNT matches
        elseif matchType == 'NT' then
            flagTemplate = 'fb'  -- Default to {{fb}} for NT/MNT matches
        else
            flagTemplate = 'fbaicon'  -- Default to {{fbaicon}} for club matches
        end
    end

    if args.flag and (flagTemplate == 'n' or flagTemplate == 'no' or flagTemplate == '0' or flagTemplate == 'false' or flagTemplate == 'null' or flagTemplate == 'none' or flagTemplate == 'noflag') then
        noFlagIcons = true  -- Hide flag icons for club matches
        if matchType == 'NT' then
            flagTemplate = isWNT and 'fbw' or 'fb'  -- Set flagTemplate to "fbw"/"fb", as disabling flags is not allowed for NT
            flagParam1 = false
        end
    end

    -- Check if flagTemplate exists and adjust if necessary
    if matchType == 'NT' and (flagTemplate ~= 'fb' and flagTemplate ~= 'fbw') then
        if not templateExists(flagTemplate) or not templateExists(flagTemplate .. '-rt') then
            flagTemplate = isWNT and 'fbw' or 'fb'
        end
    elseif not noFlagIcons and flagTemplate ~= 'fbaicon' then
        if not templateExists(flagTemplate) then
            flagTemplate = 'fbaicon'
        end
    end

    local legs = (args.legs == '1' or args.legs == 'n' or args.legs == 'no' or args.legs == 'false' or args.legs == 'null' or args.legs == 'none' or args.legs == 'single' or args.legs == 'one') and 0 or tonumber(args.legs) or 2
    if legs and legs < 0 then
        legs = 2
    end
    local teamWidth = (tonumber(args['team_width']) and args['team_width'] .. 'px') or '250px'
    local scoreWidth = (tonumber(args['score_width']) and args['score_width'] .. 'px') or '80px'
    local boldWinner = not (args.bold_winner == 'n' or args.bold_winner == 'no' or args.bold_winner == '0' or args.bold_winner == 'false' or args.bold_winner == 'null')
    local colorWinner = args.color_winner and (args.color_winner == 'y' or args.color_winner == 'yes' or args.color_winner == '1' or args.color_winner == 'true')
    local matchesStyle = args.matches_style
    local isFBRStyle = matchesStyle and matchesStyle:upper() == "FBR"
    local isHA = args.h_a == 'y' or args.h_a == 'yes' or args.h_a == '1' or args.h_a == 'true'
    local disableAwayGoals = args.away_goals == 'n' or args.away_goals == 'no' or args.away_goals == '0' or args.away_goals == 'false' or args.away_goals == 'null'
    local disableSmallText = args.small_text == 'n' or args.small_text == 'no' or args.small_text == '0' or args.small_text == 'false' or args.small_text == 'null'
    local noWrap = args.nowrap and (args.nowrap == 'y' or args.nowrap == 'yes' or args.nowrap == '1' or args.nowrap == 'true')

    local tableClass = 'wikitable'
    local tableStyle = 'text-align: center;'
    if args.collapsed and (args.collapsed == 'y' or args.collapsed == 'yes' or args.collapsed == '1' or args.collapsed == 'true') then
        tableClass = 'wikitable mw-collapsible mw-collapsed'
        tableStyle = tableStyle .. ' width: 100%;'
    end
    if noWrap then
        tableStyle = tableStyle .. ' white-space: nowrap;'
    end
    if fontSize then
        tableStyle = tableStyle .. ' font-size: ' .. fontSize .. '%;'
    end

    -- Create the table element
    local table = root:tag('table')
        :addClass(tableClass)
        :cssText(tableStyle)
    if args.id then
        table:attr('id', args.id)  -- Optional id parameter to allow anchor to table
    end

    -- Add FBR legend if isFBRStyle is true
    if isFBRStyle and legs == 0 then
        root:node(createFBRLegend())
        isHA = true
    end

    -- Add a caption to table if the "caption" parameter is passed
    if args.caption then
        table:tag('caption'):wikitext(args.caption)
    end

    -- Count number of columns
    local colCount = 3 + legs

    -- Add a title row above column headings if the "title" parameter is passed
    if args.title then
        local titleRow = table:tag('tr')
        titleRow:tag('th')
            :attr('colspan', colCount)
            :attr('scope', 'colgroup')
            :wikitext(args.title)
    end

    -- Create the header row with team and score columns
    local header = table:tag('tr')
    local defaultTeam1 = isHA and 'Home' or 'Team 1'
    local defaultTeam2 = isHA and 'Away' or 'Team 2'
    header:tag('th')
        :attr('scope', 'col')
        :css('text-align', 'right')
        :css('width', teamWidth)
        :wikitext(args['team1'] or defaultTeam1)
    header:tag('th')
        :attr('scope', 'col')
        :css('width', scoreWidth)
        :wikitext(args['aggregate'] or legs == 0 and 'Score' or '[[Aggregate score|<abbr title="Aggregate score">Agg.</abbr>]]<span class="sr-only" style="border: 0; clip: rect(0, 0, 0, 0); clip-path: polygon(0px 0px, 0px 0px, 0px 0px); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; white-space: nowrap;">Tooltip Aggregate score</span>')
    header:tag('th')
        :attr('scope', 'col')
        :css('text-align', 'left')
        :css('width', teamWidth)
        :wikitext(args['team2'] or defaultTeam2)

    -- Add columns for each leg if applicable
    if legs > 0 then
        for leg = 1, legs do
            local legHeading

            -- Check if "legN" parameter is present
            if args['leg' .. leg] then
                legHeading = args['leg' .. leg]
            else
                -- Check if "leg_prefix" parameter is present
                if args.leg_prefix then
                    -- Check if leg_prefix is y, yes, 1, or true
                    if args.leg_prefix == 'y' or args.leg_prefix == 'yes' or args.leg_prefix == '1' or args.leg_prefix == 'true' then
                        legHeading = 'Leg ' .. leg
                    else
                        legHeading = args.leg_prefix .. ' ' .. leg
                    end
                -- Check if "leg_suffix" parameter is present and does not equal y, yes, 1, or true
                elseif args.leg_suffix and args.leg_suffix ~= 'y' and args.leg_suffix ~= 'yes' and args.leg_suffix ~= '1' and args.leg_suffix ~= 'true' then
                    legHeading = ordinal(leg) .. ' ' .. args.leg_suffix
                else
                    legHeading = ordinal(leg) .. ' leg'
                end
            end

            header:tag('th')
                :attr('scope', 'col')
                :css('width', scoreWidth)
                :wikitext(legHeading)
        end
    end

    local step = (matchType == 'NT' and 3 or (noFlagIcons and 3 or 5)) + legs  -- Determine the step size based on the match type and presence of flag icons
    local i = 1
    while anyParameterPresent(i, step, args) do
        local rowIndex = math.floor((i - 1) / step) + 1
        local headingParam = args['heading' .. rowIndex]
        -- Add a heading above a given row in the table
        if headingParam then
            local headingRow = table:tag('tr')
            headingRow:tag('td')
                :attr('colspan', colCount)
                :css('background', 'whitesmoke')
                :wikitext('<strong>' .. headingParam .. '</strong>')
        end

        local row = table:tag('tr')
        local team1, team2, aggregateScore, team1Icon, team2Icon, team1Variant, team2Variant
        local team1Winner, team2Winner, manualBold, manualColor, isDraw = false, false, false, false, false
        local leg1Score, leg2Score = false, false
        local team1Asterick, team2Asterick = false, false

        -- Process rows for both national team and club matches
        if matchType == 'NT' then
            team1 = args[i]
            if team1 and team1:match("^%s*%*") then
                team1 = team1:gsub("^%s*%*", "")
                team1Asterick = true
            else
                team1, team1Variant = processIcon(args[i])
            end
            aggregateScore = args[i+1]
            team2 = args[i+2]
            if team2 and team2:match("^%s*%*") then
                team2 = team2:gsub("^%s*%*", "")
                team2Asterick = true
            else
                team2, team2Variant = processIcon(args[i+2])
            end
        else
            team1 = args[i]
            if noFlagIcons then
                aggregateScore = args[i+1]
                team2 = args[i+2]
            else
                team1Icon, team1Variant = processIcon(args[i+1])
                aggregateScore = args[i+2]
                team2 = args[i+3]
                team2Icon, team2Variant = processIcon(args[i+4])
            end
        end

        -- Name the 1st/2nd leg scores for two-legged ties
        if legs == 2 then
            if matchType == 'NT' or noFlagIcons then
                leg1Score = args[i+3]
                leg2Score = args[i+4]
            else
                leg1Score = args[i+5]
                leg2Score = args[i+6]
            end
        end

        -- Clean the aggregate score
        local cleanAggregate = cleanScore(aggregateScore)
        -- Format and rewrite anchor links for aggregate score
        aggregateScore = format_score(aggregateScore, noWrap)
        if baselink ~= '' then
            aggregateScore = rewriteAnchorLinks(aggregateScore, baselink)
        end

        -- Determine the winning team on aggregate
        team1, team2, team1Winner, team2Winner, manualBold, manualColor, isDraw, aggregateScore = determineWinner(cleanAggregate, matchType, team1, team2, boldWinner, colorWinner, aggregateScore, isFBRStyle, legs, leg1Score, leg2Score, disableAwayGoals)

        -- Add background-color for winning team if set by user
        local team1Style = 'text-align: right;'
        local team2Style = 'text-align: left;'
        if team1Winner and (colorWinner or manualColor) then
            team1Style = team1Style .. ' background-color: #CCFFCC;'
        end
        if team2Winner and (colorWinner or manualColor) then
            team2Style = team2Style .. ' background-color: #CCFFCC;'
        end

        -- Generate text to display for each team
        local team1Text, team2Text
        if matchType == 'NT' then
            if flagParam1 then  -- Check whether youth team flag template with age level is used
                team1Text = (not team1Asterick and team1 ~= "" and team1 ~= nil) and (expandTemplate(frame, flagTemplate .. '-rt', {flagParam1, team1, variant = team1Variant})) or (team1 ~= nil and team1 or "")
                team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate, {flagParam1, team2, variant = team2Variant})) or (team2 ~= nil and team2 or "")
            else  -- Use standard national team flag template without age level
                team1Text = (not team1Asterick and team1 ~= "" and team1 ~= nil) and (expandTemplate(frame, flagTemplate .. '-rt', {team1, variant = team1Variant})) or (team1 ~= nil and team1 or "")
                team2Text = (not team2Asterick and team2 ~= "" and team2 ~= nil) and (expandTemplate(frame, flagTemplate, {team2, variant = team2Variant})) or (team2 ~= nil and team2 or "")
            end
        else
            team1Text = noFlagIcons and (team1 or '') or ((team1Icon ~= "" and team1Icon ~= nil) and ((team1 or '') .. ' ' .. expandTemplate(frame, flagTemplate, {team1Icon, variant = team1Variant})) or (team1 or ''))
            team2Text = noFlagIcons and (team2 or '') or ((team2Icon ~= "" and team2Icon ~= nil) and (expandTemplate(frame, flagTemplate, {team2Icon, variant = team2Variant}) .. ' ' .. (team2 or '')) or (team2 or ''))
        end

        -- When set by user, adds blank flags when string is used for a team instead of national team flag template
        if fillBlanks then
            if matchType == 'NT' then
                if team1Asterick then
                    team1Text = team1Text .. ' <span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span>'
                end
                if team2Asterick then
                    team2Text = '<span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span> ' .. team2Text
                end
            elseif not noFlagIcons then
                if not team1Icon or team1Icon == "" then
                    team1Text = team1Text .. ' <span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span>'
                end
                if not team2Icon or team2Icon == "" then
                    team2Text = '<span class="flagicon">[[File:Flag placeholder.svg|25x17px|link=]]</span> ' .. team2Text
                end
            end
        end

        -- Create aggregate score cell with conditional styling
        local aggregateStyle = ''
        if legs == 0 and not disableSmallText and aggregateScore ~= '' and checkSmallText(aggregateScore) then
            aggregateStyle = 'font-size: ' .. smallFontSize .. '%;'
        end
        if isFBRStyle and legs == 0 then
            if team1Winner then
                aggregateStyle = aggregateStyle .. 'background-color: #BBF3FF;'
            elseif team2Winner then
                aggregateStyle = aggregateStyle .. 'background-color: #FFBBBB;'
            elseif isDraw then
                aggregateStyle = aggregateStyle .. 'background-color: #FFFFBB;'
            end
        elseif isDraw then
            aggregateStyle = aggregateStyle .. 'background-color: #FFFFBB;'
        end

        -- Create rows for aggregate score and team names, bolded if set by user
        row:tag('td'):cssText(team1Style):wikitext((team1Winner and (boldWinner or manualBold) and team1Text ~= '') and ('<strong>' .. team1Text .. '</strong>') or team1Text)
        row:tag('td'):cssText(aggregateStyle ~= '' and aggregateStyle or nil):wikitext(aggregateScore)
        row:tag('td'):cssText(team2Style):wikitext((team2Winner and (boldWinner or manualBold) and team2Text ~= '') and ('<strong>' .. team2Text .. '</strong>') or team2Text)

        -- Add columns for each leg score if applicable
        if legs > 0 then
            for leg = 1, legs do
                local legIndex = i + 4 + leg + (matchType == 'NT' and -2 or (noFlagIcons and -2 or 0))
                local legScore = args[legIndex]
                if legScore ~= "nil" then
                    if legScore == "null" then
                        if solidCell then
                            row:tag('td'):css('background', '#BBBBBB')
                        else
                            row:tag('td'):wikitext('—')
                        end
                    else
                        -- Format and rewrite anchor links for leg scores
                        legScore = format_score(legScore, noWrap)
                        if baselink ~= '' then
                            legScore = rewriteAnchorLinks(legScore, baselink)
                        end
                        local legStyle = ''
                        if not disableSmallText and legScore ~= '' and checkSmallText(legScore) then
                            legStyle = 'font-size: ' .. smallFontSize .. '%;'
                        end
                        -- Write cells for legs
                        row:tag('td'):cssText(legStyle ~= '' and legStyle or nil):wikitext(legScore)
                    end
                end
            end
        end

        i = i + step
    end

    return tostring(root)
end

return p