Module:Television infoboxes disambiguation check/sandbox

require("strict")

local libraryUtil = require("libraryUtil")

-----------------------------------------------------------------------
-- DisambiguationPattern "class"
-----------------------------------------------------------------------
local function DisambiguationPattern(o)
	local obj = o or { pattern = "", type = 0 }
	libraryUtil.makeCheckSelfFunction(
		"Television infoboxes disambiguation check",
		"DisambiguationPattern",
		obj,
		"Television infoboxes disambiguation check object"
	)
	return obj
end

-----------------------------------------------------------------------
-- Constants
-----------------------------------------------------------------------
local DAB_VALID = {
	[true] = "valid",
	[false] = "invalid"
}

local CATEGORY_INCORRECT = "[[Category:Television articles with incorrect naming style]]"

local validationTypeList = {
	VALIDATION_TYPE_YEAR_COUNTRY = 1,
	VALIDATION_TYPE_YEAR = 2,
	VALIDATION_TYPE_COUNTRY = 3,
	VALIDATION_TYPE_YEAR_SEASON_NUMBER = 4,
	VALIDATION_TYPE_COUNTRY_SEASON_NUMBER = 5,
	VALIDATION_TYPE_SEASON_NUMBER = 6,
	VALIDATION_TYPE_YEAR_COUNTRY_SEASON_NUMBER = 8
}

local debugMessageList = {
	DEBUG_EMPTY_TITLE = "Debug: Error: Empty title.",
	DEBUG_NO_DAB = "Debug: No disambiguation.",
	DEBUG_TITLE_ON_EXCEPTION = "Debug: Title on exception list.",
	DEBUG_VALID_FORMAT = "Debug: Using a valid format.",
	DEBUG_NOT_VALID_FORMAT = "Debug: Not a valid format.",
	DEBUG_YEAR_COUNTRY = "Debug: Using a valid format with an extended Year and Country - {}.",
	DEBUG_YEAR = "Debug: Using a valid format with an extended Year - {}.",
	DEBUG_COUNTRY = "Debug: Using a valid format with an extended Country - {}.",
	DEBUG_INCORRECT_STYLE = "Debug: Using a valid format but using an incorrect extended style.",
	DEBUG_INCORRECT_INFOBOX = "Debug: Using incorrect infobox - {}.",
	DEBUG_YEAR_SEASON_NUMBER = "Debug: Using a valid format with an extended Year and Season number - {}.",
	DEBUG_COUNTRY_SEASON_NUMBER = "Debug: Using a valid format with an extended Country and Season number - {}.",
	DEBUG_SEASON_NUMBER = "Debug: Using a valid format with a Season number - {}.",
	DEBUG_YEAR_COUNTRY_SEASON_NUMBER = "Debug: Using a valid format with an extended Year, Country and Season number - {}."
}

-----------------------------------------------------------------------
-- Validation helpers
-----------------------------------------------------------------------
local function validateTwoParameters(a, b)
	return not not (a and b)
end

local function validateSeasonNumber(seasonNumber)
	if not seasonNumber then
		return false
	end
	return tonumber(seasonNumber:sub(1, 1)) ~= 0
end

local function validateYear(year)
	return year and #year == 4
end

local function validateCountryAdjective(adj)
	if not adj or adj == "" then
		return false
	end
	local data = mw.loadData("Module:Country adjective")
	return not not data.getCountryFromAdj[adj]
end

-----------------------------------------------------------------------
-- Pattern validation engine
-----------------------------------------------------------------------
local function validatePatterns(disambiguation, patternList)
	local year, adjective, seasonNumber
	local isYearValid, isAdjectiveValid, isSeasonNumberValid

	for i = 1, #patternList do
		local p = patternList[i]
		if disambiguation:match(p.pattern) then

			-- YEAR + COUNTRY
			if p.type == validationTypeList.VALIDATION_TYPE_YEAR_COUNTRY then
				year, adjective = disambiguation:match(p.pattern)
				isYearValid = validateYear(year)
				isAdjectiveValid = validateCountryAdjective(adjective)
				local ok = validateTwoParameters(isYearValid, isAdjectiveValid)
				return ok, debugMessageList.DEBUG_YEAR_COUNTRY:gsub("{}", DAB_VALID[ok])

			-- YEAR
			elseif p.type == validationTypeList.VALIDATION_TYPE_YEAR then
				year = disambiguation
				isYearValid = validateYear(year)
				return isYearValid, debugMessageList.DEBUG_YEAR:gsub("{}", DAB_VALID[isYearValid])

			-- COUNTRY
			elseif p.type == validationTypeList.VALIDATION_TYPE_COUNTRY then
				adjective = disambiguation
				isAdjectiveValid = validateCountryAdjective(adjective)
				return isAdjectiveValid, debugMessageList.DEBUG_COUNTRY:gsub("{}", DAB_VALID[isAdjectiveValid])

			-- YEAR + SEASON
			elseif p.type == validationTypeList.VALIDATION_TYPE_YEAR_SEASON_NUMBER then
				year, seasonNumber = disambiguation:match(p.pattern)
				isYearValid = validateYear(year)
				isSeasonNumberValid = validateSeasonNumber(seasonNumber)
				local ok = validateTwoParameters(isYearValid, isSeasonNumberValid)
				return ok, debugMessageList.DEBUG_YEAR_SEASON_NUMBER:gsub("{}", DAB_VALID[ok])

			-- COUNTRY + SEASON
			elseif p.type == validationTypeList.VALIDATION_TYPE_COUNTRY_SEASON_NUMBER then
				adjective, seasonNumber = disambiguation:match(p.pattern)
				isAdjectiveValid = validateCountryAdjective(mw.text.trim(adjective))
				isSeasonNumberValid = validateSeasonNumber(seasonNumber)
				local ok = validateTwoParameters(isAdjectiveValid, isSeasonNumberValid)
				return ok, debugMessageList.DEBUG_COUNTRY_SEASON_NUMBER:gsub("{}", DAB_VALID[ok])

			-- SEASON ONLY
			elseif p.type == validationTypeList.VALIDATION_TYPE_SEASON_NUMBER then
				seasonNumber = disambiguation:match(p.pattern)
				isSeasonNumberValid = validateSeasonNumber(seasonNumber)
				return isSeasonNumberValid, debugMessageList.DEBUG_SEASON_NUMBER:gsub("{}", DAB_VALID[isSeasonNumberValid])

			-- YEAR + COUNTRY + SEASON
			elseif p.type == validationTypeList.VALIDATION_TYPE_YEAR_COUNTRY_SEASON_NUMBER then
				year, adjective, seasonNumber = disambiguation:match(p.pattern)
				isYearValid = validateYear(year)
				isAdjectiveValid = validateCountryAdjective(mw.text.trim(adjective))
				isSeasonNumberValid = validateSeasonNumber(seasonNumber)
				local ok = validateTwoParameters(isYearValid, isAdjectiveValid)
				ok = validateTwoParameters(ok, isSeasonNumberValid)
				return ok, debugMessageList.DEBUG_YEAR_COUNTRY_SEASON_NUMBER:gsub("{}", DAB_VALID[ok])
			end
		end
	end

	return false, debugMessageList.DEBUG_INCORRECT_STYLE
end

-----------------------------------------------------------------------
-- Disambiguation type validation
-----------------------------------------------------------------------
local function validateDisambiguationType(disambiguation, typeList)
	local extended = disambiguation
	local count = 0

	for i = 1, #typeList do
		local t = typeList[i]
		extended, count = extended:gsub(t, "")
		extended = mw.text.trim(extended)
		if count ~= 0 then
			break
		end
	end
	return count ~= 0, extended
end

local function validateDisambiguation(invoker, disambiguation, typeList, patternList)
	if #typeList ~= 0 then
		local ok, extended = validateDisambiguationType(disambiguation, typeList)

		if not ok then
			return false, debugMessageList.DEBUG_NOT_VALID_FORMAT
		end

		if extended == "" then
			return true, debugMessageList.DEBUG_VALID_FORMAT
		end

		if invoker ~= "infobox television season" then
			disambiguation = extended
		end
	end

	return validatePatterns(disambiguation, patternList)
end

-----------------------------------------------------------------------
-- Incorrect infobox detection
-----------------------------------------------------------------------
local function isPageUsingIncorrectInfobox(invoker, disambiguation, otherList)

	-- Only skip incorrect-infobox detection for SEASON pages.
	-- Season pages often have disambiguations like:
	--   "season 1", "series 2", "season 1 TV series"
	-- These should NOT trigger incorrect-infobox errors.
	if invoker == "infobox television season" then
		if disambiguation:match("^[Ss]eason%s+%d+") 
			or disambiguation:match("^[Ss]eries%s+%d+") then
			return false
		end
	end

	-- Normal incorrect-infobox detection
	for k, v in pairs(otherList) do
		if disambiguation:match(k) then
			return true, v, debugMessageList.DEBUG_INCORRECT_INFOBOX:gsub("{}", k)
		end
	end

	return false
end

-----------------------------------------------------------------------
-- Exception list
-----------------------------------------------------------------------
local function isOnExceptionList(title, list)
	for _, v in ipairs(list) do
		if v == title or title:match(v) then
			return true
		end
	end
	return false
end

-----------------------------------------------------------------------
-- Disambiguation extraction (supports new naming)
-----------------------------------------------------------------------
-- Get the disambiguation text and support both:
-- - Parenthetical only: "Big Brother (American TV series)"
-- - Parenthetical + season: "Big Brother (American TV series) season 5"
-- - Season only: "Lost season 1"
local function getDisambiguation(title)
	local match = require("Module:String")._match

	-- Last parenthetical chunk, if any.
	local paren = match(title, "%s%((.-)%)", 1, -1, false, "")

	-- Trailing "season X" or "series X", if any (X can be wrong; validation will decide).
	local base, seasonPart = title:match("^(.-)%s+(season%s+.+)$")
	if not base then
		base, seasonPart = title:match("^(.-)%s+(series%s+.+)$")
	end

	if paren ~= "" and seasonPart then
		-- Example: "Big Brother (American TV series) season 5"
		-- Returns: "American TV series season 5"
		return paren .. " " .. seasonPart
	elseif paren ~= "" then
		-- Example: "Big Brother (American TV series)"
		-- Returns: "American TV series"
		return paren
	elseif seasonPart then
		-- Example: "Lost season 1"
		-- Returns: "season 1"
		return seasonPart
	else
		return ""
	end
end

-----------------------------------------------------------------------
-- Utility
-----------------------------------------------------------------------
local function isEmpty(x)
	return not x or x == ""
end

-----------------------------------------------------------------------
-- Main entry point
-----------------------------------------------------------------------
local function main(title, invoker, typeList, patternList, exceptionList, otherInfoboxList, invalidTitleStyleList)
	if isEmpty(title) then
		return "", debugMessageList.DEBUG_EMPTY_TITLE
	end

	if isOnExceptionList(title, exceptionList) then
		return "", debugMessageList.DEBUG_TITLE_ON_EXCEPTION
	end

	if invoker == "infobox television season" then
		for i = 1, #invalidTitleStyleList do
			if title:find(invalidTitleStyleList[i]) then
				return CATEGORY_INCORRECT, debugMessageList.DEBUG_NOT_VALID_FORMAT
			end
		end
	end

	local disambiguation = getDisambiguation(title)

	if isEmpty(disambiguation) then
		return "", debugMessageList.DEBUG_NO_DAB
	end

	local wrong, category, dbg = isPageUsingIncorrectInfobox(invoker, disambiguation, otherInfoboxList)
	if wrong then
		return category, dbg
	end

	local ok, dbg2 = validateDisambiguation(invoker, disambiguation, typeList, patternList)

	if not ok then
		return CATEGORY_INCORRECT, dbg2
	end

	return "", dbg2
end

-----------------------------------------------------------------------
-- Export
-----------------------------------------------------------------------
return {
	main = main,
	DisambiguationPattern = DisambiguationPattern,

	VALIDATION_TYPE_YEAR_COUNTRY = validationTypeList.VALIDATION_TYPE_YEAR_COUNTRY,
	VALIDATION_TYPE_YEAR = validationTypeList.VALIDATION_TYPE_YEAR,
	VALIDATION_TYPE_COUNTRY = validationTypeList.VALIDATION_TYPE_COUNTRY,
	VALIDATION_TYPE_YEAR_SEASON_NUMBER = validationTypeList.VALIDATION_TYPE_YEAR_SEASON_NUMBER,
	VALIDATION_TYPE_COUNTRY_SEASON_NUMBER = validationTypeList.VALIDATION_TYPE_COUNTRY_SEASON_NUMBER,
	VALIDATION_TYPE_SEASON_NUMBER = validationTypeList.VALIDATION_TYPE_SEASON_NUMBER,
	VALIDATION_TYPE_YEAR_COUNTRY_SEASON_NUMBER = validationTypeList.VALIDATION_TYPE_YEAR_COUNTRY_SEASON_NUMBER
}