Actions

Module

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

------------------------------------------
-- Module:Group
-- Membership summaries + Membership Table
------------------------------------------
local p = {}

--------------------------------------------------
-- Shared helpers
--------------------------------------------------

-- Trim whitespace
local function trim(s)
	if not s then return "" end
	return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end

-- Merge direct #invoke args with template args
local function getArgs(frame)
	local parent = frame:getParent()
	local out = {}

	-- 1) Args passed directly to #invoke
	for k, v in pairs(frame.args or {}) do
		if v ~= "" then
			out[k] = v
		end
	end

	-- 2) Fallback to parent template args
	if parent and parent.args then
		for k, v in pairs(parent.args) do
			if out[k] == nil or out[k] == "" then
				out[k] = v
			end
		end
	end
	return out
end

-- Helper: expand {{p|Name}} or {{p|Name|Label}} and return the rendered link
local function makePLink(frame, name, label)
	if not name or name == "" then
		return ""
	end

	local args = { name }
	if label and label ~= "" then
		args[2] = label
	end

	return frame:expandTemplate{
		title = "p",
		args  = args
	}
end

-- Replace empty/blank with  
local function cell(value)
	if not value or value == "" then
		return " "
	end
	return value
end

-- Extract plain display text from member input (for memberN_nolink=yes)
-- Supports: plain, [[Target]], [[Target|Label]], {{p|Target}}, {{p|Target|Label}}
local function displayTextOnly(raw)
	raw = trim(raw or "")
	if raw == "" then return "" end

	-- [[Target|Label]] or [[Target]]
	local target, label = raw:match("^%[%[([^%]|]+)%|([^%]]+)%]%]$")
	if target then return trim(label) end
	target = raw:match("^%[%[([^%]]+)%]%]$")
	if target then return trim(target) end

	-- {{p|Target|Label}} or {{p|Target}}
	local t1, t2 = raw:match("^{{%s*[Pp]%s*|%s*([^|}]+)%s*|%s*([^}]+)%s*}}$")
	if t1 then return trim(t2) end
	t1 = raw:match("^{{%s*[Pp]%s*|%s*([^}]+)%s*}}$")
	if t1 then return trim(t1) end

	-- plain text
	return raw
end

-- Parse member input that may be:
--  * plain title: Superman (Clark Kent)
--  * wikilink: [[Superman (Clark Kent)|Supernova]]
--  * wikilink: [[Superman (Clark Kent)]]
--  * template: {{p|Superman (Clark Kent)|Supernova}}
--  * template: {{p|Superman (Clark Kent)}}
-- Returns: targetTitle (string), label (string or ""), rawKind ("plain"/"wikilink"/"p"/"plain"), rawOriginal
local function parseMemberValue(raw)
	raw = trim(raw or "")
	if raw == "" then
		return "", "", "plain", ""
	end

	-- [[Target|Label]] or [[Target]]
	do
		local target, label = raw:match("^%[%[([^%]|]+)%|([^%]]*)%]%]$")
		if target then
			return trim(target), trim(label or ""), "wikilink", raw
		end
		target = raw:match("^%[%[([^%]]+)%]%]$")
		if target then
			return trim(target), "", "wikilink", raw
		end
	end

	-- {{p|Target|Label}} or {{p|Target}}
	-- (simple parse; assumes no nested pipes in the params)
	do
		local target, label = raw:match("^%{%{%s*[Pp]%s*%|%s*([^|}]+)%s*%|%s*([^}]+)%s*%}%}$")
		if target then
			return trim(target), trim(label or ""), "p", raw
		end
		target = raw:match("^%{%{%s*[Pp]%s*%|%s*([^}]+)%s*%}%}$")
		if target then
			return trim(target), "", "p", raw
		end
	end

	-- Fallback: treat as plain text (could be unrecognized wikitext)
	return raw, "", "plain", raw
end

-- True if a wiki page exists
local function pageExists(titleText)
	titleText = trim(titleText or "")
	if titleText == "" then return false end
	local t = mw.title.new(titleText)
	return (t ~= nil) and t.exists
end

-- Build output for member names:
-- - If target exists: use {{p|Target}} or {{p|Target|Label}}
-- - If target does not exist: output label (or target) as plain text (no redlink)
-- You can force redlinks by changing this behavior easily (see note below).
local function renderMember(frame, rawMemberValue, labelOverride)
	local target, labelFromRaw = parseMemberValue(rawMemberValue)
	target = trim(target)
	if target == "" then return "" end

	local label = trim(labelOverride or "")
	if label == "" then
		label = trim(labelFromRaw or "")
	end

	if pageExists(target) then
		-- Use your template p for consistent styling
		if label ~= "" then
			return makePLink(frame, target, label)
		end
		return makePLink(frame, target)
	end

	-- No page: plain text
	if label ~= "" then return label end
	return target
end

-- For the gallery we need the *target title* (plain), plus the best label.
local function getMemberTargetAndLabel(rawMemberValue, labelOverride)
	local target, labelFromRaw = parseMemberValue(rawMemberValue)
	target = trim(target)
	local label = trim(labelOverride or "")
	if label == "" then label = trim(labelFromRaw or "") end
	return target, label
end

-- Work out if a member is active and/or support based on status/role
local function classifyMember(args, base)
	local statusRaw = args[base .. "_status"] or ""
	local status    = statusRaw:lower()
	local role      = (args[base .. "_role"] or ""):lower()

	-- "Active" if status contains "active" but not "inactive", OR blank
	local hasActive   = status:find("active",   1, true) ~= nil
	local hasInactive = status:find("inactive", 1, true) ~= nil
	local isActive
	if status == "" then
		isActive = true  -- blank = treat as not-former (current)
	else
		isActive = hasActive and not hasInactive
	end

	-- "Support" if role mentions support/staff/crew/business
	local isSupport = false
	if role ~= "" then
		if role:find("support", 1, true)
			or role:find("staff", 1, true)
			or role:find("crew",  1, true)
			or role:find("business", 1, true)
		then
			isSupport = true
		end
	end
	return isActive, isSupport, statusRaw
end

-- Group is considered "defunct" if status says so
local function isDefunctStatus(args)
	local s = (args.status or ""):lower()
	if s == "" then
		return false
	end

	if s:find("defunct",   1, true)
		or s:find("disbanded", 1, true)
		or s:find("dissolved", 1, true)
		or s:find("inactive",  1, true)
	then
		return true
	end

	return false
end

--------------------------------------------------
-- ERA helpers
--------------------------------------------------

local function splitEraValues(s)
	if not s or s == "" then return {} end
	local t = {}
	for part in s:gmatch("[^;]+") do
		table.insert(t, trim(part):lower())
	end
	return t
end

-- Member matches era filter?
local function memberMatchesEra(memberEraRaw, eraFilterRaw)
	-- If no filter is requested, everyone matches
	local eraFilter = trim(eraFilterRaw or "")
	if eraFilter == "" then
		return true
	end

	-- If a filter IS requested, only members with an era value can match
	local memberEras = splitEraValues(memberEraRaw or "")
	if #memberEras == 0 then
		return false
	end

	-- era filter itself can be a semicolon list: "original; satellite"
	local wanted = splitEraValues(eraFilter)
	if #wanted == 0 then
		-- Edge case: malformed filter, treat as no filter
		return true
	end

	for _, m in ipairs(memberEras) do
		for _, w in ipairs(wanted) do
			if m == w then
				return true
			end
		end
	end

	return false
end

-- Pick the right image for a member, with optional era-specific override
local function getMemberImage(args, base, eraFilterRaw)
	-- generic image
	local img = trim(args[base .. "_image"] or "")

	-- if an era filter is active, try era-specific image first
	local eraFilter = trim(eraFilterRaw or "")
	if eraFilter ~= "" then
		local eras = splitEraValues(eraFilter)
		if #eras > 0 then
			-- we take the first era in the filter for the suffix
			local suffix = "_" .. eras[1]  -- eras[1] is already lowercased by splitEraValues
			local key = base .. "_image" .. suffix
			local eraImg = trim(args[key] or "")
			if eraImg ~= "" then
				img = eraImg
			end
		end
	end

	return img
end

--------------------------------------------------
-- Summary lists (Current / Former / Supporting)
--------------------------------------------------

-- Collect member names into a comma-separated list of links
-- mode = "current", "former", "support"
local function collectMembers(frame, args, max, mode, eraFilter)
	max = tonumber(max)
		or tonumber(args.max_members)
		or tonumber(args.max)
		or 50   -- default scan limit

	eraFilter = eraFilter or ""

	local list = {}

	for i = 1, max do
		local base = "member" .. i
		local name = args[base]
		
		-- Step 1: detect nolink
		local nolinkRaw = trim(args[base .. "_nolink"] or ""):lower()
		local nolink = (nolinkRaw == "yes" or nolinkRaw == "true" or nolinkRaw == "1")

		-- Must have a name and match the era (if any)
		if name and name ~= "" and memberMatchesEra(args[base .. "_era"], eraFilter) then
			local isActive, isSupport = classifyMember(args, base)
			local include = false

			if mode == "current" then
				-- Current = active, not support
				include = isActive and not isSupport
			elseif mode == "former" then
				-- Former = not active and not support
				include = (not isActive) and not isSupport
			elseif mode == "support" then
				-- Supporting crew only
				include = isSupport
			end

			if include then
				-- Respect optional _label override + nolink
				local nameLabel = trim(args[base .. "_label"] or "")

				-- Parse the raw member value (plain / [[ ]] / {{p| }})
				local targetTitle, parsedLabel, rawKind, rawOriginal = parseMemberValue(name)

				-- Final label precedence:
				-- 1) explicit memberN_label
				-- 2) label found in [[Target|Label]] or {{p|Target|Label}}
				-- 3) blank (use targetTitle)
				local finalLabel = nameLabel
				if finalLabel == "" then
					finalLabel = parsedLabel or ""
				end

				local outText

				if nolink then
					-- Plain text only (prefer explicit _label if given)
					outText = (nameLabel ~= "" and nameLabel) or displayTextOnly(name)
				else
					-- Always link (red links OK). Normalize to {{p|Title|Label}}
					if rawKind == "plain" or rawKind == "wikilink" or rawKind == "p" then
						outText = makePLink(frame, targetTitle, finalLabel)
					else
						-- Unknown formatting: keep as-is; if an override label exists, show it as plain text
						outText = (nameLabel ~= "" and nameLabel) or (rawOriginal or name)
					end
				end

				if mode == "support" then
					-- Show simplified status tag: (active) or (former)
					local tag = isActive and "active" or "former"
					outText = outText .. " (" .. tag .. ")"
				end

				table.insert(list, outText)
			end
		end
	end

	return table.concat(list, ", ")
end

-- Count how many members fall into a given mode ("current", "former", "support")
local function countMembers(args, max, mode, eraFilter)
	max = tonumber(max)
		or tonumber(args.max_members)
		or tonumber(args.max)
		or 50

	eraFilter = eraFilter or ""

	local count = 0

	for i = 1, max do
		local base = "member" .. i
		local name = args[base]

		-- Step 1: detect nolink
		local nolinkRaw = trim(args[base .. "_nolink"] or ""):lower()
		local nolink = (nolinkRaw == "yes" or nolinkRaw == "true" or nolinkRaw == "1")
		
		-- Must have a name and match era
		if name and name ~= "" and memberMatchesEra(args[base .. "_era"], eraFilter) then
			local isActive, isSupport = classifyMember(args, base)
			local include = false

			if mode == "current" then
				include = isActive and not isSupport
			elseif mode == "former" then
				include = (not isActive) and not isSupport
			elseif mode == "support" then
				include = isSupport
			end

			if include then
				count = count + 1
			end
		end
	end

	return count
end

-- All non-support members (ignores "current"/"former" split)
local function collectMembersAll(frame, args, max)
	max = tonumber(max)
		or tonumber(args.max_members)
		or tonumber(args.max)
		or 50

	local list = {}

	for i = 1, max do
		local base = "member" .. i
		local name = args[base]

		-- Step 1: detect nolink
		local nolinkRaw = trim(args[base .. "_nolink"] or ""):lower()
		local nolink = (nolinkRaw == "yes" or nolinkRaw == "true" or nolinkRaw == "1")
		
		if name and name ~= "" then
			local _, isSupport = classifyMember(args, base)
			if not isSupport then
				table.insert(list, renderMember(frame, name, args[base .. "_label"]))
			end
		end
	end

	return table.concat(list, ", ")
end

function p.membershipSummaryAll(frame)
	local args = getArgs(frame)
	return collectMembersAll(frame, args, args.max_members)
end

function p.membershipSummaryAllBlock(frame)
	local list = p.membershipSummaryAll(frame)
	if list == "" then
		return ""
	end
	return "<br><b>Members:</b> " .. list
end

-- "Current Members:" list (names only; label is in template)
function p.membershipSummaryCurrent(frame)
	local args = getArgs(frame)

	-- Honour manual override
	if args.current_members and args.current_members ~= "" then
		return args.current_members
	end

	local eraFilter = args.era or ""
	return collectMembers(frame, args, args.max_members, "current", eraFilter)
end

-- "Former Members:" full block (label + names), or blank if none
function p.membershipSummaryFormerBlock(frame)
	local args      = getArgs(frame)
	local eraFilter = args.era or ""
	local list = collectMembers(frame, args, args.max_members, "former", eraFilter)

	if list == "" then
		return ""
	end

	return "<br><b>Former Members:</b> " .. list
end

-- "Support Staff:" full block, or blank if none
function p.supportingSummary(frame)
	local args      = getArgs(frame)
	local eraFilter = args.era or ""
	local list = collectMembers(frame, args, args.max_members, "support", eraFilter)

	if list == "" then
		return ""
	end

	return "<br><b>Support Staff:</b> " .. list
end

--------------------------------------------------
-- Membership TABLE with era + column control
--------------------------------------------------
function p.membershipTable(frame)
	local args  = getArgs(frame)
	local max   = tonumber(args.max_members) or tonumber(args.max) or 300
	local eraFilter = args.era or ""

	-- include_support = "no" → hide support from table
	local includeSupport = true
	if args.include_support then
		local v = trim(args.include_support):lower()
		if v == "no" or v == "false" or v == "0" then
			includeSupport = false
		end
	end

	-- Pre-scan to discover:
	--  * any members at all
	--  * whether any member has joined / active_from (for smart header)
	local hasAny = false
	local hasActiveFromData = false
	local hasJoinedData = false

	for i = 1, max do
		local base = "member" .. i
		local name = trim(args[base] or "")

		if name ~= "" then
			hasAny = true

			local joined = trim(args[base .. "_joined"] or "")
			local activeFrom = trim(args[base .. "_active_from"] or "")

			if joined ~= "" then
				hasJoinedData = true
			end
			if activeFrom ~= "" then
				hasActiveFromData = true
			end
		end
	end

	if not hasAny then
		return ""
	end

	-- Parse membership_columns= as an ordered token list (strict, order-preserving).
	-- Canonical tokens:
	--   joined, first, status, note, note2, era
	local function parseColumnsList(s)
		s = trim((s or ""):lower())
		if s == "" then return {} end
	
		local out = {}
		for token in s:gmatch("[^,%s]+") do
			token = trim(token)

			-- Strict canonicalization (EXACT matches only)
			if token == "note2" then
				token = "note2"
			elseif token == "note" or token == "note1" then
				token = "note"
			elseif token == "joined" then
				token = "joined"
			elseif token == "first" then
				token = "first"
			elseif token == "status" then
				token = "status"
			elseif token == "era" then
				token = "era"
			else
				-- ignore unknown tokens (prevents accidental mangling)
				token = nil
			end

			if token then
				out[#out+1] = token
			end
		end

		return out
	end

	local function hasToken(list, t)
		for _, v in ipairs(list) do
			if v == t then return true end
		end
		return false
	end

	-- Auto-insert joined if missing:
	-- default to 2nd column, but if "first" exists, put joined AFTER first.
	local function ensureJoined(list)
		if hasToken(list, "joined") then
			return list
		end

		for i, v in ipairs(list) do
			if v == "first" then
				table.insert(list, i + 1, "joined")
				return list
			end
		end

		table.insert(list, 1, "joined")
		return list
	end

	local colList = ensureJoined(parseColumnsList(args.membership_columns or ""))

	-- Note column header names (only used if note/note2 are in membership_columns)
	local note1Name = "Note"
	if args.membership_note1 and args.membership_note1 ~= "" then
		note1Name = args.membership_note1
	elseif args.membership_note and args.membership_note ~= "" then
		note1Name = args.membership_note
	end

	local note2Name = "Note 2"
	if args.membership_note2 and args.membership_note2 ~= "" then
		note2Name = args.membership_note2
	end

	-- Joined / Active from header (smart)
	local joinedHeader
	if hasJoinedData and not hasActiveFromData then
		joinedHeader = "Joined"
	elseif hasActiveFromData and not hasJoinedData then
		joinedHeader = "Active From"
	else
		joinedHeader = "Joined / Active From"
	end

	-- Build table
	local out = {}
	out[#out+1] = '{| class="wikitable sortable membership-table" style="width:100%; border:1px; font-size:90%; border:0px;"'

	-- Header row
	out[#out+1] = "|-"
	out[#out+1] = "! Member"

	-- Optional columns in requested order (with joined inserted if missing)
	for _, col in ipairs(colList) do
		if col == "joined" or col == "active" or col == "activefrom" then
			out[#out+1] = "!! " .. joinedHeader
		elseif col == "first" then
			out[#out+1] = "!! First Appearance"
		elseif col == "status" then
			out[#out+1] = "!! Status"
		elseif col == "note" or col == "note1" then
			out[#out+1] = "!! " .. note1Name
		elseif col == "note2" then
			out[#out+1] = "!! " .. note2Name
		elseif col == "era" then
			out[#out+1] = "!! Era"
		end
	end

	-- Data rows
	for i = 1, max do
		local base = "member" .. i
		local rawMember = trim(args[base] or "")

		-- memberN_nolink = yes → show name as plain text (no links)
		local nolinkRaw = trim(args[base .. "_nolink"] or ""):lower()
		local nolink = (nolinkRaw == "yes" or nolinkRaw == "true" or nolinkRaw == "1")

		if rawMember ~= "" then
			if memberMatchesEra(args[base .. "_era"], eraFilter) then
				local isActive, isSupport, statusRaw = classifyMember(args, base)

				-- Skip support staff if include_support = no
				if (not isSupport) or includeSupport then
					out[#out+1] = "|-"

					-- Member name (supports plain / [[ ]] / {{p| }} + _label + _nolink)
					local labelOverride = trim(args[base .. "_label"] or "")

					-- Parse raw member value
					local targetTitle, parsedLabel, rawKind, rawOriginal = parseMemberValue(rawMember)

					-- Label precedence: explicit _label > label found in markup > blank
					local finalLabel = labelOverride
					if finalLabel == "" then
						finalLabel = parsedLabel or ""
					end

					local memberText
					if nolink then
						-- Plain text only (prefer explicit _label if present)
						memberText = (labelOverride ~= "" and labelOverride) or displayTextOnly(rawMember)
					else
						-- Always link (red links OK) using {{p|Title|Label}}
						if rawKind == "plain" or rawKind == "wikilink" or rawKind == "p" then
							memberText = makePLink(frame, targetTitle, finalLabel)
						else
							-- Unknown formatting: keep as-is; if _label exists, show that instead
							memberText = (labelOverride ~= "" and labelOverride) or (rawOriginal or rawMember)
						end
					end

					-- Member cell first (always)
					out[#out+1] = "| " .. cell(memberText)

					-- Remaining cells in the same order as the headers
					for _, col in ipairs(colList) do
						if col == "joined" or col == "active" or col == "activefrom" then
							local joined = trim(args[base .. "_joined"] or "")
							local activeFrom = trim(args[base .. "_active_from"] or "")
							local joinedDisplay = ""
							if joined ~= "" then
								joinedDisplay = joined
							elseif activeFrom ~= "" then
								joinedDisplay = activeFrom
							end
							out[#out+1] = "| " .. cell(joinedDisplay)

						elseif col == "first" then
							local first = trim(args[base .. "_first"] or "")
							out[#out+1] = "| " .. cell(first)

						elseif col == "status" then
							local status = trim(statusRaw or "")
							out[#out+1] = "| " .. cell(status)

						elseif col == "note" or col == "note1" then
							local n1 = trim(args[base .. "_note"] or "")
							out[#out+1] = "| " .. cell(n1)

						elseif col == "note2" then
							local n2 = trim(args[base .. "_note2"] or "")
							out[#out+1] = "| " .. cell(n2)

						elseif col == "era" then
							local er = trim(args[base .. "_era"] or "")
							out[#out+1] = "| " .. cell(er)
						end
					end
				end
			end
		end
	end

	out[#out+1] = "|}"
	return table.concat(out, "\n")
end

--------------------------------------------------
-- Membership GALLERY (uses Template:Member)
--------------------------------------------------
function p.membershipGallery(frame)
	local args  = getArgs(frame)
	local max   = tonumber(args.max_members) or tonumber(args.max) or 50
	local eraFilter = args.era or ""

	-- include_support = "no" → hide support from gallery
	local includeSupport = true
	if args.include_support then
		local v = trim(args.include_support):lower()
		if v == "no" or v == "false" or v == "0" then
			includeSupport = false
		end
	end

	-- How many cards per row
	local perRow = tonumber(args.gallery_per_row) or 5

	local out = {}
	local count = 0
	local anyShown = false

	for i = 1, max do
		local base = "member" .. i

		-- Raw member input as entered in the template.
		-- IMPORTANT: keep this in its own variable and never reuse it, or parsing may silently break.
		local rawMember = trim(args[base] or "")

		-- memberN_nolink = yes
		-- Means: show name + image, but never link to a profile page.
		local nolinkRaw = trim(args[base .. "_nolink"] or ""):lower()
		local nolink = (nolinkRaw == "yes" or nolinkRaw == "true" or nolinkRaw == "1")

		if rawMember ~= "" then
			if memberMatchesEra(args[base .. "_era"], eraFilter) then
				local isActive, isSupport, statusRaw = classifyMember(args, base)

				-- Skip support staff if include_support = no
				if (not isSupport) or includeSupport then
					if not anyShown then
						-- Open container + first row (only once)
						out[#out+1] = '<div style="overflow-x:auto;"><table><tr>'
						anyShown = true
					end

					-- Membership metadata
					local joined     = trim(args[base .. "_joined"])
					local activeFrom = trim(args[base .. "_active_from"])
					local status     = trim(statusRaw)
					local note       = trim(args[base .. "_note"])

					-- Image name (explicit or era-specific)
					local image      = getMemberImage(args, base, eraFilter)

					-- Parse member input.
					-- Supports: plain title, [[Target]], [[Target|Label]], {{p|Target}}, {{p|Target|Label}}
					-- Returns a clean target title suitable for: linking, default image name
					local targetTitle, parsedLabel = parseMemberValue(rawMember)

					-- SAFETY NET:
					-- The gallery *must* always have a non-empty title in field 0, otherwise images become "File:.png".
					-- If parsing fails, fall back to display-only text.
					if not targetTitle or targetTitle == "" then
						targetTitle = displayTextOnly(rawMember)
					end

					-- Label precedence:
					--   1) explicit memberN_label
					--   2) label embedded in [[Target|Label]] or {{p|Target|Label}}
					--   3) blank (Template:Member will derive a name)
					local labelOverride = trim(args[base .. "_label"] or "")
					local finalLabel = labelOverride
					if finalLabel == "" then
						finalLabel = parsedLabel or ""
					end

					-- Pack the member string in the exact format expected by Template:Member
					local packed = table.concat({
						targetTitle,
						joined,
						activeFrom,
						status,
						note,
						image
					}, "\\")

					-- Arguments passed to Template:Member
					local memberCardArgs = {
						member = packed
					}

					-- Optional display label
					if finalLabel ~= "" then
						memberCardArgs.label = finalLabel
					end

					-- If nolink=yes:
					--  - name is rendered as plain text
					--  - image is shown but NOT clickable
					--  - no profile page is required
					if nolink then
						memberCardArgs.nolink = "yes"
					end

					local memberCard = frame:expandTemplate{
						title = "Member",
						args  = memberCardArgs
					}

					-- Add card cell
					out[#out+1] = "<td>" .. memberCard .. "</td>"
					count = count + 1

					-- Wrap to next row after perRow cards
					if (count % perRow) == 0 then
						out[#out+1] = "</tr><tr>"
					end
				end
			end
		end
	end

	if not anyShown then
		return ""
	end

	out[#out+1] = "</tr></table></div>"
	return table.concat(out, "\n")
end

--------------------------------------------------
-- Router for membership pages
-- mode = "table", "gallery", "summaries"
--------------------------------------------------
function p.membershipRouter(frame)
	local args = getArgs(frame)
	local mode = (args.mode or args.membership_mode or "table"):lower()

	if mode == "gallery" then
		return p.membershipGallery(frame)

elseif mode == "summaries" then
	-- Decide whether to list names or just link to the membership section
	local summaryLimit = tonumber(args.summary_limit) or 50
	local max = args.max_members or args.max

	local c = countMembers(args, max, "current")
	local f = countMembers(args, max, "former")
	local s = countMembers(args, max, "support")
	local total = c + f + s

	-- If there are *lots* of members, just link to the MEMBERSHIP section or page
	if total > summaryLimit then
		local title = mw.title.getCurrentTitle()
		local linkText = "Membership list"

		local linked = trim(args.membership_linked or ""):lower()
		if linked == "yes" or linked == "true" or linked == "1" then
			return '<br><b>Members:</b> See [['
				.. title.prefixedText
				.. ' Membership|' .. linkText .. ']]'
		end
	
		return '<br><b>Members:</b> See [['
			.. title.prefixedText
			.. '#MEMBERSHIP|' .. linkText .. ']]'
	end

	-- Small enough to list inline:
	-- if the team is defunct / disbanded / dissolved / inactive,
	-- collapse current+former into a single "Members:" line.
	if isDefunctStatus(args) then
		local members = collectMembersAll(frame, args, max)
		if members == "" then
			return ""
		end
		local support = p.supportingSummary(frame)
		return "<br><b>Members:</b> " .. members .. support
	end

	-- Normal case: Current / Former / Support Staff
	local current = p.membershipSummaryCurrent(frame)
	if current ~= "" then
		current = "<br><b>Current Members:</b> " .. current
	end

	local former  = p.membershipSummaryFormerBlock(frame)
	local support = p.supportingSummary(frame)

	return current .. former .. support

	else
		-- Default: table
		return p.membershipTable(frame)
	end
end

return p