-- This module implements {{DYK checklist}}.

-- Load modules
require('strict')
local yesno = require('Module:Yesno')
local data = mw.loadData('Module:DYK checklist/data')
local responseIcons = data.responseIcons
local statusIcons = data.statusIcons

-- Template for making collapsed sections.
local COLLAPSED_WIKITABLE = [[
{| class="collapsible collapsed" border="1" style="border-collapse:collapse;"
|-
! style="font-weight:normal; " | %s
|-
| %s
|}]]

-- Template for making uncollapsed sections.
local UNCOLLAPSED_WIKITABLE = [[
{| border="1" style="border-collapse:collapse;"
|-
| %s
|}]]

--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------

-- Make the given key in args lowercase if when lowercased it equals val.
local function makeArgLowerCase(args, key, val)
	if args[key] and string.lower(args[key]) == val then
		args[key] = val
	end
end

-- Normalize the args table to make it easier to work with elsewhere in the
-- module.
local function normalizeArguments(args)
	-- Consolidate aliases
	args.plagiarismfree = args.plagiarismfree or args.plagarismfree
	args.plagarismfree = nil

	-- Normalize special values
	makeArgLowerCase(args, 'hookcited', 'agf')
	makeArgLowerCase(args, 'picfree', 'na')
	makeArgLowerCase(args, 'qpq', 'na')
end

-- If any of the keys in the keys array are in the table t, return true;
-- otherwise, return false.
local function anyKeysInTable(t, keys)
	for i, key in ipairs(keys) do
		if t[key] then
			return true
		end
	end
	return false
end

-- Make a wikitext error message.
local function makeWikitextError(msg)
	return string.format([['''<span style="color: red;">%s</span>''']], msg)
end

-- Format a user-supplied string for display in error messages.
-- This prevents input from being displayed as special wiki markup, converts
-- multi-line strings to a single line, and truncates long strings so that they
-- are easier to read.
local function escapeUserString(s)
	if #s > 28 then
		s = s:sub(1, 12) .. '...' .. s:sub(-12, -1)
	end
	s = s:gsub("\n", " ")
	return mw.text.nowiki(s)
end

-- Make a collapsed wikitable with the given header and content. 
local function makeCollapsedWikitable(header, content)
	return string.format(COLLAPSED_WIKITABLE, header, content)
end

-- Make an uncollapsed wikitable with the given content. 
local function makeUncollapsedWikitable(content)
	return string.format(UNCOLLAPSED_WIKITABLE, content)
end

-- Make a bulleted list from an array of strings.
local function makeBulletedList(items)
	local ret = {}
	for i, item in ipairs(items) do
		ret[i] = '* ' .. item
	end
	return table.concat(ret, '\n')
end

-- Make a checklist item from the given issue and status.
local function makeChecklistItem(issue, status, defaultMarker)
	if not status then
		return string.format('%s: %s', issue, responseIcons.UNKNOWN)
	elseif yesno(status) then
		return string.format('%s: %s', issue, responseIcons.YES)
	else
		return string.format(
			'%s: %s - %s',
			issue,
			defaultMarker or responseIcons.NO,
			status
		)
	end
end

-- Return true if all issues have been resolved; return false otherwise.
-- mainIssues is an array of tables as passed to makeSection. otherIssues is a
-- string value or nil (again, as passed to makeSection).
local function allIssuesAreResolved(mainIssues, otherIssues)
	if otherIssues then
		return false
	end
	for i, t in ipairs(mainIssues) do
		if t.isResolved == false
			or (
				t.isResolved ~= true
				and not yesno(t.status)
			)
		then
			return false
		end
	end
	return true
end

-- Assemble a section of the DYK checklist.
local function makeSection(options)
	local issues = {}

	-- Add main issues
	options.mainIssues = options.mainIssues or {}
	for i, t in ipairs(options.mainIssues) do
		local checklistItem
		if t.isResolved then
			checklistItem = makeChecklistItem(t.issue, t.status, responseIcons.YES)
		else
			checklistItem = makeChecklistItem(t.issue, t.status)
		end
		table.insert(issues, checklistItem)
	end

	-- Add other issues
	if options.otherIssues then
		table.insert(issues, makeChecklistItem('Other problems', options.otherIssues))
	end

	-- Make the section output.
	local content = makeBulletedList(issues)
	if allIssuesAreResolved(options.mainIssues, options.otherIssues) then
		return makeCollapsedWikitable(options.resolvedHeader, '\n' .. content)
	else
		return options.unresolvedHeader .. '\n' .. content
	end
end

--------------------------------------------------------------------------------
-- Section functions
-- Each of these functions makes a single section of the DYK checklist.
--------------------------------------------------------------------------------

local function makeGeneralEligibilitySection(args)
	return makeSection{
		unresolvedHeader = "'''General eligiblity:'''",
		resolvedHeader = "'''General:''' Article is new enough and long enough",
		mainIssues = {
			{
				issue = '[[WP:Did you know#New|New Enough]]',
				status = args.newness,
			},
			{
				issue = '[[WP:Did you know#Long enough|Long Enough]]',
				status = args.length,
			},
		},
		otherIssues = args.eligibilityother,
	}
end

local function makePolicyComplianceSection(args)
	return makeSection{
		unresolvedHeader = "'''Policy compliance:'''",
		resolvedHeader = "'''Policy:''' Article is sourced, neutral, and free of copyright problems",
		mainIssues = {
			{
				issue = '[[WP:Citing sources|Adequate sourcing]]',
				status = args.sourced,
			},
			{
				issue = '[[WP:NPOV|Neutral]]',
				status = args.neutral,
			},
			{
				issue = 'Free of [[Wikipedia:Copyright violations|copyright violations]], [[Wikipedia:Plagiarism|plagiarism]], and [[WP:close paraphrasing|close paraphrasing]]',
				status = args.plagiarismfree,
			},
		},
		otherIssues = args.policyother,
	}
end

local function makeHookEligibilitySection(args)
	-- Deal with AGF special case for hook citations
	local hookCiteStatus, isHookSourceAGF
	if args.hookcited == 'agf' then
		hookCiteStatus = 'Offline/paywalled citation accepted in good faith'
		isHookSourceAGF = true
	else
		hookCiteStatus = args.hookcited
		isHookSourceAGF = nil -- use default behaviour
	end

	-- Generate output
	return makeSection{
		unresolvedHeader = "'''Hook eligiblity:'''",
		resolvedHeader = "'''Hook:''' Hook has been verified by provided inline citation",
		mainIssues = {
			{
				issue = '[[WP:Did you know#Cited hook|Cited]]',
				status = hookCiteStatus,
				isResolved = isHookSourceAGF
			},
			{
				issue = 'Interesting',
				status = args.hookinterest,
			},
		},
		otherIssues = args.hookother,
	}
end

local function makeImageEligibilitySection(args)
	if args.status
		and (args.picfree == 'na' or not args.picfree)
		and not args.picused
		and not args.picclear
	then
		return nil
	end
	return makeSection{
		unresolvedHeader = "'''Image eligibility:'''",
		resolvedHeader = "'''Image:''' Image is freely licensed, used in the article, and clear at 100px.",
		mainIssues = {
			{
				issue = '[[WP:ICTIC|Freely licensed]]',
				status = args.picfree,
			},
			{
				issue = '[[WP:Did you know#Pictures|Used in article]]',
				status = args.picused,
			},
			{
				issue = 'Clear at 100px',
				status = args.picclear,
			},
		},
	}
end

local function makeQPQSection(args)
	-- The QPQ section is different enough from the other sections that we
	-- will just do everything here rather than trying to use the makeSection
	-- function.
	local isDone = yesno(args.qpq)
	if isDone == true then
		return makeUncollapsedWikitable("'''QPQ''': Done.")
	elseif args.qpq == 'na' then
		return makeUncollapsedWikitable("'''QPQ''': None required.")
	else
		local ret = makeChecklistItem(
			"'''[[Wikipedia:Did you know#QPQ|QPQ]]'''",
			isDone == false and 'Not done' or args.qpq
		)
		return ret .. '<br />'
	end
end

local function makeStatusSection(args)
	if not args.status then
		return makeWikitextError('Review is incomplete - please fill in the "status" field')
	elseif args.status ~= 'y'
		and args.status ~= '?'
		and args.status ~= 'maybe'
		and args.status ~= 'no'
		and args.status ~= 'again'
	then
		return makeWikitextError(string.format(
			'Invalid status "%s" - use one of "y", "?", "maybe", "no" or "again"',
			escapeUserString(args.status)
		))
	end

	local ret = {}
	table.insert(ret, "'''Overall''': ")
	local isOK = yesno(args.status)
	if isOK == true then
		if args.hookcited == 'agf' then
			table.insert(ret, statusIcons.YES_AGF)
		else
			table.insert(ret, statusIcons.YES)
		end
	elseif isOK == false then
		table.insert(ret, statusIcons.NO)
	elseif args.status == '?' then
		table.insert(ret, statusIcons.QUESTION)
	elseif args.status == 'maybe' then
		table.insert(ret, statusIcons.MAYBE)
	elseif args.status == 'again' then
		table.insert(ret, statusIcons.AGAIN)
	end
	if args.comments then
		table.insert(ret, ' ')
		table.insert(ret, args.comments)
	end
	if args.sign then
		table.insert(ret, ' ')
		table.insert(ret, args.sign)
	end
	return table.concat(ret)
end

--------------------------------------------------------------------------------
-- Exports
--------------------------------------------------------------------------------

local p = {}

function p._main(args)
	-- Normalize the args table to make it easier to work with in other
	-- functions.
	normalizeArguments(args)

	-- Check whether the review has been started.
	local params = {
		'newness',
		'length',
		'eligibilityother',
		'sourced',
		'neutral',
		'plagiarismfree',
		'policyother',
		'hookcited',
		'hookother',
		'hookcited',
		'picfree',
		'picused',
		'picclear',
	}
	if not anyKeysInTable(args, params) then
		return 'Review not started'
	end

	-- The review has been started, so assemble all the review sections.
	local funcs = {
		makeGeneralEligibilitySection,
		makePolicyComplianceSection,
		makeHookEligibilitySection,
		makeImageEligibilitySection,
		makeQPQSection,
		makeStatusSection,
	}
	local ret = {}
	for i, func in ipairs(funcs) do
		table.insert(ret, func(args))
	end
	return table.concat(ret, '\n')
end

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:DYK checklist',
	})
	return p._main(args)
end

return p