local p = {}

--- Returns a formatted link to the list of episodes article.
--- @param listOfEpisodesArticle string
--- @return string
local function getListOfEpisodesLink(listOfEpisodesArticle)
	local listOfEpisodesPage = mw.title.new
	if listOfEpisodesPage and listOfEpisodesPage.exists and listOfEpisodesPage.redirectTarget ~= mw.title.getCurrentTitle then
		return string.format
	end
end

--- Returns an article link.
--- @param article string The article's title.
--- @param pipedLink string The piped link.
--- @return string
local function getArticleLink
	if (pipedLink == nil or pipedLink == "") then
		return "" .. article .. ""
	end
	return "" .. pipedLink .. ""
end

--- Returns the show name and season number from a title.
--- @param showName string The show's title.
--- @return nil | number | string, nil | number | string
local function getShowNameAndSeasonNumberFromShowName(showName)
	local _, _, showNameModified, seasonNumber = string.find(showName, "(.*)%s+(%d+)$")
	return showNameModified, seasonNumber
end

--- Returns the current season number from the disambiguation.
--- @param disambiguation string The article's disambiguation.
--- @return string
local function getCurrentSeasonNumberFromDisambiguation(disambiguation)
	return match
end

--- Returns the type of word used for "season" in the disambiguation.
---
--- The returned value can be one of three options: "season", "series", "story arc" or "specials".
--- @param disambiguation string The article's disambiguation.
--- @return string
local function getSeasonType(disambiguation)
	for _, seasonType in pairs do
		if (string.find) then
			return seasonType
		end
	end
	return "시즌"
end

--- Returns the disambiguation without the "(year) TV series," part.
--- @param disambiguation string The article's disambiguation.
--- @return string
local function getShortDisambiguation(disambiguation)
	local shortDisambiguation, _ = string.gsub
	return shortDisambiguation
end

--- Returns the disambiguation from the title.
--- @param title string The article's title.
--- @return string | nil
local function getDisambiguation(title)
	local disambiguation = match(title, "%s%((.-)%)", 1, -1, false, "")
	if (disambiguation == "") then
		return nil
	end
	return disambiguation
end

--- Returns the TV program's disambiguation.
--- @param disambiguation string The disambiguation used in the season's article title.
--- @return string
local function getTVProgramDisambiguation(disambiguation)
	if (not disambiguation) then
		return ""
	end

	-- Check if the disambiguation is normal 'season #' or 'series #'.
	-- If so, remove disambiguation.
	if string.match or string.match then
		return ""
	end

	local disambiguationStyle = " (%s)"
	-- Check if the disambiguation is extended and has 'TV series' and isn't just season #.
	-- Only leave the TV series disambiguation, not including the season #.
	-- Example: Teenage Mutant Ninja Turtles  will return '1987 TV series'.
	if (string.find) then
		local shortDisambiguation, _ = disambiguation:match("^(.*),")
		if shortDisambiguation then
			return string.format
		end
	end

	-- Check if the disambiguation is extended with country adjective.
	-- Example: The Office (American season 2) will return "American season 2".
	-- Keep only country adjective.
	local countryDisambiguation = disambiguation:match("^(.*) 시즌 %d*") or disambiguation:match("^(.*) 시리즈 %d*")
	local data = mw.loadData("Module:Country adjective")
	local valid_result = data.getCountryFromAdj[countryDisambiguation]
	-- Check if the country adjective is valid.
	if (valid_result) then
		-- Add 'TV series' suffix
		return string.format
	end

	-- Not a known disambiguation style. Use whatever was used in the title or manually added.
	-- Note: might not be a valid style link.
	return string.format
end

--- Returns the show's name from the title.
--- @param title string The article's title.
--- @return string
local function getShowName(title)
	local name, _ = mw.ustring.gsub
	return name
end

--- Returns "true" if the given link is valid; nil otherwise.
--- A link is valid in the following cases:
---	-- A season article exists.
---	-- A redirect exists to a season section.
---
--- A link is invalid in the following cases:
---	-- A season article or redirect do not exist.
---	-- A redirect exists, but it is a general redirect and not for any specific season section.
---
--- Note: Return values are not booleans as the returned value is used in template space.
--- @param title string The article's title.
--- @return string | nil
local function isLinkValid(title)
	local article = mw.title.new(title)

	-- Article or redirect do not exist; Not a valid link.
	if (article == nil or not article.exists) then
		return nil
	end

	local redirectTarget = article.redirectTarget

	-- Article exists and is not a redirect; Valid link.
	if (not redirectTarget) then
		return "true"
	end

	local fullLink = redirectTarget.fullText
	local isSection = fullLink:find("#")

	-- Article is a section redirect; Valid link.
	if (isSection) then
		return "true"
	end

	-- Article is a general redirect; Not a valid link.
	return nil
end

--- Returns a season article title and a piped link.
---
--- The following are the supported season naming styles:
---	-- Style: )
---		Example: Lost (season 2).
---		Example: Doctor Who (series 2).
---	-- Style: )
---		Example: The Office (American season 2).
---		Example: X Factor (British series 2).
---	-- Style: )
---		Example: Big Brother 2 (American season).
---	-- Style: )
---		Example: Teenage Mutant Ninja Turtles 
---	-- Style: )
---		Example: Love Island 
--- @param title string The article's title.
--- @param seasonNumberDiff number The number difference between the current season and the other season.
--- @return string, string
local function getArticleTitleAndPipedLink
	local disambiguation = getDisambiguation(title)
	local shortDisambiguation
	local seasonType
	local seasonNumber
	local pipedLink = ""

	if (disambiguation) then
		shortDisambiguation = getShortDisambiguation(disambiguation)
		seasonType = getSeasonType(shortDisambiguation)
		seasonNumber = getCurrentSeasonNumberFromDisambiguation(shortDisambiguation)
		pipedLink = seasonType:gsub .. " "
	end

	local showName = getShowName(title)
	local showNameModified
	if (seasonNumber == nil or seasonNumber == "") then
		-- Not a valid next/prev season link
		if (not string.match(showName, "%s+(%d+)$")) then
			return "", nil
		end

		showNameModified, seasonNumber = getShowNameAndSeasonNumberFromShowName(showName)
	end

	if (tonumber(seasonNumber) == nil) then
		return "", nil
	end

	seasonNumber = seasonNumber + seasonNumberDiff
	pipedLink = pipedLink .. seasonNumber

	-- Titles such as "Big Brother 1 (American season)""
	if (showNameModified and disambiguation) then
		return showNameModified .. " " .. seasonNumber .. " (" .. disambiguation .. ")", pipedLink

	-- Titles such as "Big Brother Brasil 1"
	elseif (showNameModified) then
		return showNameModified .. " " .. seasonNumber, nil

	-- Standard titles such as "Lost (season 1)"
	else
		local newDisambiguation, _ = string.gsub
		return showName .. " (" .. newDisambiguation .. ")", pipedLink
	end
end

--- Returns the article's title either from args (usually from /testcases) or from the page itself.
--- @param frame table The frame invoking the module.
--- @return string
local function getTitle(frame)
	local getArgs = require("Module:Arguments").getArgs
	local args = getArgs(frame)
	local title = args.title

	if (not title) then
		title = mw.title.getCurrentTitle.text
	end
	return title
end

--- Returns "true" if the given season link is valid; nil otherwise.
--- @param frame table The frame invoking the module.
--- @param seasonNumberDiff number The number difference between the current season and the other season.
--- @return string | nil
local function isSeasonLinkValid
	local title = getTitle(frame)
	local articleTitle, _ = getArticleTitleAndPipedLink
	return isLinkValid(articleTitle)
end

--- Returns a season article link.
--- @param frame table The frame invoking the module.
--- @param seasonNumberDiff number The number difference between the current season and the other season.
--- @return string
local function getSeasonArticleLink
	local title = getTitle(frame)
	local articleTitle, pipedLink = getArticleTitleAndPipedLink
	return getArticleLink
end

--- Returns "true" if the season link for the next season is valid; nil otherwise.
--- @param frame table The frame invoking the module.
--- @return string | nil
function p.isNextSeasonLinkValid(frame)
	return isSeasonLinkValid
end

--- Returns "true" if the season link for the previous season is valid; nil otherwise.
--- @param frame table The frame invoking the module.
--- @return string | nil
function p.isPrevSeasonLinkValid(frame)
	return isSeasonLinkValid
end

--- Returns "true" if the season link for the previous or next season is valid; nil otherwise.
--- @param frame table The frame invoking the module.
--- @return string | nil
function p.isPrevOrNextSeasonLinkValid(frame)
	if (p.isPrevSeasonLinkValid(frame) == "true") then
		return "true"
	end
	return p.isNextSeasonLinkValid(frame)
end

--- Returns the next season article title.
--- @param frame table The frame invoking the module.
--- @return string
function p.getNextSeasonArticle(frame)
	return getSeasonArticleLink
end

--- Returns the previous season article title.
--- @param frame table The frame invoking the module.
--- @return string
function p.getPrevSeasonArticle(frame)
	return getSeasonArticleLink
end

--- Returns the type of season word used - "season" or "series".
--- @param frame table The frame invoking the module.
--- @return string
function p.getSeasonWord(frame)
	local title = getTitle(frame)
	local disambiguation = getDisambiguation(title)
	if (not disambiguation) then
		return ""
	end

	local shortDisambiguation = getShortDisambiguation(disambiguation)
	local seasonType = getSeasonType(shortDisambiguation)
	if (seasonType == "특집" or "스페셜") then
		seasonType = "시즌"
	end

	return seasonType
end

--- Returns the relevant text for the sub-header field.
---
--- The text is returned in the format of "Season #" or "Series #",
--- depending on either what the article disambiguation uses, or on the manually entered parameters of the infobox.
--- @param frame table The frame invoking the module.
--- @return string | nil
function p.getInfoboxSubHeader(frame)
	local getArgs = require("Module:Arguments").getArgs
	local args = getArgs(frame)

	local seasonType
	local seasonNumber
	if (args['시즌번호']) then
		seasonType = "시즌"
		seasonNumber = args['시즌번호']
	elseif (args['시리즈번호']) then
		seasonType = "시리즈"
		seasonNumber = args['시리즈번호']
	end

	local title = getTitle(frame)
	local disambiguation = getDisambiguation(title)
	if (not seasonNumber and disambiguation) then
		local shortDisambiguation = getShortDisambiguation(disambiguation)

		seasonType = getSeasonType(shortDisambiguation)
		if (seasonType == "특집" or "스페셜") then
			return shortDisambiguation
		end
		seasonType = seasonType:sub:upper .. seasonType:sub(2)
		seasonNumber = getCurrentSeasonNumberFromDisambiguation(shortDisambiguation)
	end

	if (seasonNumber and seasonNumber ~= "") then
		return seasonType .. " " .. seasonNumber
	end

	return nil
end


--- Returns a formatted link to the list of episodes article.
---
--- The returned link is in the style of:
--- [List of |List of episodes]
---
--- The link will only return if the page exists.
--- @param frame table The frame invoking the module.
--- @return string | nil
function p.getListOfEpisodes(frame)
	local getArgs = require("Module:Arguments").getArgs
	local args = getArgs(frame)

	if (args.link) then
		-- Parameter should be unformatted.
		if (string.find) then
			local delink = require("Module:Delink")._delink
			args.link = delink
		end

		return getListOfEpisodesLink(args.link)
	end

	local title = getTitle(frame)
	local showName = getShowName(title)

	if (showName) then
		local disambiguation = getDisambiguation(title)
		local TVProgramDisambiguation = getTVProgramDisambiguation(disambiguation)
		local listOfEpisodesArticle = string.format
		return getListOfEpisodesLink(listOfEpisodesArticle)
	end
end