Module:SouthernStraitCorridorOps

From MicrasWiki
Revision as of 19:54, 5 June 2026 by NewZimiaGov (talk | contribs)
Jump to navigationJump to search

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

-- Module:SouthernStraitCorridorOps
-- Southern Strait corridor display.
-- Pulls from:
--   Module:WarLeagueDivisionOpsBVR
--   Module:MissionaryDailyOpsBVR
--   Module:HatchMinistryDailyOps
--
-- This module intentionally overrides the displayed city/node to Mylecia
-- for all rendered War League, Temple Bank, and Hatch Ministry corridor rows.

local p = {}

--------------------------------------------------------------------------------
-- Configuration
--------------------------------------------------------------------------------

local SOUTHERN_STRAIT_CITY = "Mylecia"
local SOUTHERN_STRAIT_CITY_FLAG = "MyleciaFlabB.jpeg"
local CITY_PAGE = "List_of_cities_in_Bassaridia_Vaeringheim"

local DEFAULT_LIMIT = 24

local HAIFAN_STAGING_CITIES = {
	["Keybir-Aviv"] = true,
	["Tel-Amin"] = true,
	["Diamandis"] = true,
	["Jogi"] = true,
	["Lewisburg"] = true,
	["Thermosalem"] = true,
	["Akróstadium"] = true,
	["Sufriya"] = true,
	["Lykopolis"] = true,
	["Mylecia"] = true,
}

--------------------------------------------------------------------------------
-- Basic helpers
--------------------------------------------------------------------------------

local function trim(s)
	return mw.text.trim(tostring(s or ""))
end

local function lower(s)
	return mw.ustring.lower(tostring(s or ""))
end

local function safeRequire(name)
	local ok, mod = pcall(require, name)
	if ok and mod then
		return mod
	end
	return nil
end

local function flagIcon(fileName, size)
	size = tonumber(size) or 20
	if not fileName or fileName == "" then
		return ""
	end
	return string.format("[[File:%s|%dpx]]", fileName, size)
end

local function myleciaCell(flagSize)
	return string.format(
		"%s [[%s#Mylecia|Mylecia]]",
		flagIcon(SOUTHERN_STRAIT_CITY_FLAG, flagSize),
		CITY_PAGE
	)
end

local function stripCellMarkup(s)
	s = tostring(s or "")
	s = s:gsub("<br%s*/?>", " ")
	s = s:gsub("%[%[File:[^%]]+%]%]", "")
	s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1")
	s = s:gsub("%[%[([^%]]+)%]%]", "%1")
	s = s:gsub("'''", "")
	s = s:gsub("''", "")
	return trim(s)
end

local function rowContainsSouthernStrait(text)
	local t = lower(stripCellMarkup(text))

	if t:find("southern strait", 1, true) then return true end
	if t:find("southern straits", 1, true) then return true end
	if t:find("former maritime markets", 1, true) then return true end
	if t:find("maritime markets", 1, true) then return true end
	if t:find("straits compliance", 1, true) then return true end
	if t:find("strait compliance", 1, true) then return true end
	if t:find("prize%-court") then return true end
	if t:find("ferry lane") then return true end
	if t:find("ferry%-lane") then return true end
	if t:find("inland transfer") then return true end
	if t:find("pilgrimage%-route") then return true end
	if t:find("harbor%-to%-road") then return true end

	for city in pairs(HAIFAN_STAGING_CITIES) do
		if t:find(lower(city), 1, true) then
			return true
		end
	end

	return false
end

local function splitWikiCells(line)
	line = tostring(line or "")
	line = line:gsub("^|%s*", "")
	local parts = mw.text.split(line, "%s*||%s*")
	for i, v in ipairs(parts) do
		parts[i] = trim(v)
	end
	return parts
end

local function firstNonEmpty(...)
	for _, v in ipairs({ ... }) do
		v = trim(v)
		if v ~= "" then
			return v
		end
	end
	return "—"
end

local function statusStyle(statusText)
	local s = lower(statusText)

	if s:find("critical", 1, true) or s:find("red", 1, true) or s:find("closed", 1, true) or s:find("no%-go") then
		return ' style="background-color:#f8d7da;"'
	end

	if s:find("high", 1, true) or s:find("orange", 1, true) or s:find("restricted", 1, true) or s:find("limited", 1, true) then
		return ' style="background-color:#fff3cd;"'
	end

	if s:find("yellow", 1, true) or s:find("medium", 1, true) or s:find("slow", 1, true) then
		return ' style="background-color:#fff3cd;"'
	end

	return ' style="background-color:#d4edda;"'
end

--------------------------------------------------------------------------------
-- Source parsers
--------------------------------------------------------------------------------

local function parseWarLeagueRows(text, args)
	local rows = {}
	local flagSize = tonumber(args.flagsize) or 20
	local filterRows = tostring(args.filter or "yes"):lower() ~= "no"

	local currentStyle = ""
	for line in tostring(text or ""):gmatch("[^\r\n]+") do
		local trimmed = trim(line)

		if trimmed:match("^|%-") then
			currentStyle = trimmed:gsub("^|%-", "")
		elseif trimmed:match("^|") and not trimmed:match("^|}") then
			local cells = splitWikiCells(trimmed)

			-- WarLeagueDivisionOpsBVR expected:
			-- Date, Division, City / Node, Daily event trigger, Urgency,
			-- Operation, Ground Forces, Naval Forces, Aerial Forces, Oversight,
			-- Compliance / Notes
			if #cells >= 10 and lower(cells[1]) ~= "date" then
				local combined = table.concat(cells, " ")
				if (not filterRows) or rowContainsSouthernStrait(combined) then
					local division = firstNonEmpty(cells[2])
					local urgency = firstNonEmpty(cells[5])
					local operation = firstNonEmpty(cells[6])
					local forces = table.concat({
						"'''Ground:''' " .. firstNonEmpty(cells[7]),
						"'''Naval:''' " .. firstNonEmpty(cells[8]),
						"'''Aerial:''' " .. firstNonEmpty(cells[9])
					}, "<br>")
					local notes = table.concat({
						firstNonEmpty(cells[4]),
						firstNonEmpty(cells[10]),
						firstNonEmpty(cells[11])
					}, "<br>")

					table.insert(rows, {
						style = currentStyle ~= "" and currentStyle or statusStyle(urgency .. " " .. notes),
						source = "'''War League'''",
						date = firstNonEmpty(cells[1]),
						unit = division,
						city = myleciaCell(flagSize),
						status = urgency,
						tasking = operation .. "<br>" .. forces,
						notes = notes,
					})
				end
			end
		end
	end

	return rows
end

local function parseTempleRows(text, args)
	local rows = {}
	local flagSize = tonumber(args.flagsize) or 20
	local filterRows = tostring(args.filter or "yes"):lower() ~= "no"

	local currentStyle = ""
	for line in tostring(text or ""):gmatch("[^\r\n]+") do
		local trimmed = trim(line)

		if trimmed:match("^|%-") then
			currentStyle = trimmed:gsub("^|%-", "")
		elseif trimmed:match("^|") and not trimmed:match("^|}") then
			local cells = splitWikiCells(trimmed)

			-- MissionaryDailyOpsBVR expected:
			-- Date, City, Region, Ops Alert, Mystery (Cult), Unit,
			-- Tasking, Temple Bank Notes, optional Threat Watch, Severity
			if #cells >= 9 and lower(cells[1]) ~= "date" then
				local combined = table.concat(cells, " ")
				if (not filterRows) or rowContainsSouthernStrait(combined) then
					local severity = cells[#cells]
					local threat = (#cells >= 10) and cells[#cells - 1] or ""
					local bankNotes = (#cells >= 10) and cells[#cells - 2] or cells[8]

					table.insert(rows, {
						style = currentStyle ~= "" and currentStyle or statusStyle(cells[4] .. " " .. severity),
						source = "'''Temple Bank'''",
						date = firstNonEmpty(cells[1]),
						unit = firstNonEmpty(cells[5], cells[6]),
						city = myleciaCell(flagSize),
						status = firstNonEmpty(cells[4], severity),
						tasking = "'''Unit:''' " .. firstNonEmpty(cells[6]) .. "<br>" .. firstNonEmpty(cells[7]),
						notes = firstNonEmpty(bankNotes) .. (threat ~= "" and ("<br>'''Threat Watch:''' " .. threat) or ""),
					})
				end
			end
		end
	end

	return rows
end

local function parseHatchRows(text, args)
	local rows = {}
	local flagSize = tonumber(args.flagsize) or 20
	local filterRows = tostring(args.filter or "yes"):lower() ~= "no"

	local currentStyle = ""
	for line in tostring(text or ""):gmatch("[^\r\n]+") do
		local trimmed = trim(line)

		if trimmed:match("^|%-") then
			currentStyle = trimmed:gsub("^|%-", "")
		elseif trimmed:match("^|") and not trimmed:match("^|}") then
			local cells = splitWikiCells(trimmed)

			-- HatchMinistryDailyOps expected:
			-- Privateer Flag, Captain, Home City, Current Station,
			-- Ops Alert, Rotation, Today's Activity
			if #cells >= 7 and lower(cells[2]) ~= "captain" then
				local combined = table.concat(cells, " ")
				if (not filterRows) or rowContainsSouthernStrait(combined) then
					table.insert(rows, {
						style = currentStyle ~= "" and currentStyle or statusStyle(cells[5] .. " " .. cells[7]),
						source = "'''Hatch Ministry'''",
						date = firstNonEmpty(cells[6]),
						unit = firstNonEmpty(cells[2]),
						city = myleciaCell(flagSize),
						status = firstNonEmpty(cells[5]),
						tasking = firstNonEmpty(cells[7]),
						notes = "'''Home city:''' " .. firstNonEmpty(cells[3]) .. "<br>'''Current station:''' " .. firstNonEmpty(cells[4]),
					})
				end
			end
		end
	end

	return rows
end

--------------------------------------------------------------------------------
-- Source loaders
--------------------------------------------------------------------------------

local function getWarLeagueRows(args)
	local wl = safeRequire("Module:WarLeagueDivisionOpsBVR")
	if not wl or type(wl.makeTable) ~= "function" then
		return {}
	end

	local year = tostring(args.year or "")
	local day = tostring(args.day or "")
	local seed = tostring(args.seed or "")

	local callArgs = {
		rows = tostring(args.warRows or args.rows or "48"),
		showlegend = "no",
	}

	if seed ~= "" then
		callArgs.seed = seed
	elseif year ~= "" and day ~= "" then
		callArgs.seed = tostring((tonumber(year) or 0) * 1000 + (tonumber(day) or 0))
	end

	local ok, result = pcall(wl.makeTable, { args = callArgs })
	if not ok then
		return {}
	end

	return parseWarLeagueRows(result, args)
end

local function getTempleRows(args)
	local miss = safeRequire("Module:MissionaryDailyOpsBVR")
	if not miss or type(miss.makeTable) ~= "function" then
		return {}
	end

	local callArgs = {
		region = tostring(args.templeRegion or args.region or "Haifan Bassaridia"),
		includeThreat = tostring(args.includeThreat or "no"),
		showEcon = "no",
		showlegend = "no",
		limit = tostring(args.templeLimit or "80"),
	}

	if args.year then callArgs.year = tostring(args.year) end
	if args.day then callArgs.day = tostring(args.day) end

	local ok, result = pcall(miss.makeTable, { args = callArgs })
	if not ok then
		return {}
	end

	return parseTempleRows(result, args)
end

local function getHatchRows(args)
	local hatch = safeRequire("Module:HatchMinistryDailyOps")
	if not hatch or type(hatch.dailyReport) ~= "function" then
		return {}
	end

	local callArgs = {
		legend = "no",
	}

	if args.year then callArgs.year = tostring(args.year) end
	if args.day then callArgs.day = tostring(args.day) end
	if args.captain then callArgs.captain = tostring(args.captain) end

	local ok, result = pcall(hatch.dailyReport, { args = callArgs })
	if not ok then
		return {}
	end

	return parseHatchRows(result, args)
end

--------------------------------------------------------------------------------
-- Rendering
--------------------------------------------------------------------------------

local function addRows(target, sourceRows, limit)
	for _, row in ipairs(sourceRows) do
		if #target >= limit then
			return
		end
		table.insert(target, row)
	end
end

local function renderTable(rows)
	local out = {}

	table.insert(out, "{| class='wikitable sortable' style='width:100%;'")
	table.insert(out, "! Source !! Date / Rotation !! Unit !! City !! Status !! Tasking !! Notes")

	if #rows == 0 then
		table.insert(out, "|-")
		table.insert(out, "| colspan='7' style='text-align:center;' | ''No Southern Strait corridor rows were returned by the source modules for this invocation.''")
	else
		for _, row in ipairs(rows) do
			table.insert(out, "|-" .. (row.style or ""))
			table.insert(out, string.format(
				"| %s || %s || %s || %s || %s || %s || %s",
				firstNonEmpty(row.source),
				firstNonEmpty(row.date),
				firstNonEmpty(row.unit),
				firstNonEmpty(row.city),
				firstNonEmpty(row.status),
				firstNonEmpty(row.tasking),
				firstNonEmpty(row.notes)
			))
		end
	end

	table.insert(out, "|}")
	return table.concat(out, "\n")
end

local function renderLegend()
	return table.concat({
		"<div style='margin-top:0.5em; font-size:90%;'>",
		"<b>Display note:</b> Southern Strait corridor rows are shown through ",
		myleciaCell(20),
		". Source-module tasking, Op-IDs, captains, units, and compliance notes are preserved; only the displayed corridor city is normalized for this page.",
		"</div>"
	}, "")
end

--------------------------------------------------------------------------------
-- Public entry points
--------------------------------------------------------------------------------

function p.makeTable(frame)
	local args = (frame and frame.args) or {}
	local limit = tonumber(args.limit) or DEFAULT_LIMIT
	local showLegend = tostring(args.showlegend or args.legend or "yes"):lower() ~= "no"
	local showHeader = tostring(args.header or "no"):lower() == "yes"

	local rows = {}

	local includeWar = tostring(args.war or "yes"):lower() ~= "no"
	local includeTemple = tostring(args.temple or "yes"):lower() ~= "no"
	local includeHatch = tostring(args.hatch or "yes"):lower() ~= "no"

	if includeWar then
		addRows(rows, getWarLeagueRows(args), limit)
	end

	if includeTemple then
		addRows(rows, getTempleRows(args), limit)
	end

	if includeHatch then
		addRows(rows, getHatchRows(args), limit)
	end

	local out = {}

	if showHeader then
		table.insert(out, "'''Southern Strait Corridor Daily Operations'''")
		table.insert(out, "")
	end

	table.insert(out, renderTable(rows))

	if showLegend then
		table.insert(out, renderLegend())
	end

	return table.concat(out, "\n")
end

function p.dailyReport(frame)
	return p.makeTable(frame)
end

function p.generateTable(frame)
	return p.makeTable(frame)
end

function p.cityCell(frame)
	local args = (frame and frame.args) or {}
	return myleciaCell(args.flagsize or 20)
end


function p.dailyReport(frame)
	return p.makeTable(frame)
end

function p.generateTable(frame)
	return p.makeTable(frame)
end

function p.cityCell(frame)
	local args = (frame and frame.args) or {}
	return myleciaCell(args.flagsize or 20)
end

function p.southernStrait(frame)
	return p.makeTable(frame)
end

return p