local export = {}

local rsplit = mw.text.split
local rfind = mw.ustring.find

function get_template_title(frame)
	return frame:getParent():getTitle()
end

function get_template_content(frame)
	local title = get_template_title(frame)
	return mw.title.new( title ):getContent()
end

function get_template_code(text)
    -- returns the transcludable code on a template page
    res = ""
    matched = false
    for part in string.gfind(text, "<onlyinclude>(.-)</onlyinclude>") do
        res = res .. part
        matched = true
    end
    code = matched and res or text

    code = string.gsub(code, "<noinclude>(.-)</noinclude>", "\1")
    code = string.gsub(code, "<noinclude>(.-)</noinclude>", "")

    return code
end

function clean_name(param_name)
    param_name = mw.text.trim(param_name)

    -- convert numeric to numbers
    if string.find(param_name, "^[0-9]+$") then
        return tonumber(param_name)
    end

    return param_name
end

function get_params(content)
    -- returns a table of all params used in the template text
    -- "used" params are any params referenced inside triple braces like {{{param}}}

	local MAGIC_WORD = {
		["FULLPAGENAME"] = true,
		["PAGENAME"] = true,
		["BASEPAGENAME"] = true,
		["NAMESPACE"] = true,
		["SUBPAGENAME"] = true,
		["SUBJECTSPACE"] = true,
		["TALKPAGENAME"] = true,
		["!"] = true,
	}

	local params = {}

	-- count any string between {{{ and | or } (minus MAGIC_WORDS) as a parameter name
	for param in string.gfind(content, "{{{([^=|{}<>]-)[|}]") do
        param = clean_name(param)
		if not MAGIC_WORD[param] then
			params[param] = {}
		end  
	end

	return params
end

function get_template_params(frame)
    -- returns a table of all params used by parent template
	local template_content = get_template_content(frame)
    template_code = get_template_code(template_content)
    return get_params(template_code)
end

function params_to_string(invalid_args)
    -- join a table of key, values pairs into a semicolon separated list
    msg = ""
	for k, v in pairs(invalid_args) do
		if msg ~= "" then
            msg = msg .. "; "
        end
		msg = msg .. k .. "=" .. v
	end
    return msg
end

function get_allowed_args(frame)
    -- returns a table of allowed params
    -- if frame.args[1] contains a comma separated list of param names, use that
    -- otherwise, detect all params used by the parent template

	local params
	if frame.args[1] then
		params = {}
		for _, param in ipairs(rsplit(frame.args[1], ",")) do
			param = clean_name(param)
			params[param] = {}
		end
	else
		params = get_template_params(frame)
	end
    return params
end

function make_warning_text(template_name, invalid_args, nowarn, noattn, nocat)
    -- generate "Invalid params" warning to be inserted into wiki page
    -- template_name (required) name of the template with invalid params
    -- invalid_args (required) table of invalid args
	-- nocat= (optional) do not included category in warning_text
	-- noattn= (optional) do not include attention seeking span in in warning_text
	-- nowarn= (optional) do not include preview warning in warning_text

	local invalid_args_string = params_to_string(invalid_args)
    msg = "Invalid params in call to [[" .. template_name .. "]]: " .. mw.text.nowiki(invalid_args_string)

    -- show warning in previewer
    warn = nowarn and "" or '<sup class="error previewonly"><small>' .. msg .. '</small></sup>'

    -- add attentionseeking message
    attn = noattn and "" or '<span class="attentionseeking" title="' .. msg .. '"></span>'

    -- categorize
    cat = nocat and "" or "[[Category:Pages using bad params when calling " .. template_name .. "]]"

    return warn .. attn .. cat 
end

function export.process(frame, allowed_params, nowarn, noattn, nocat)
	-- This is desgined to be called by other Lua modules instead of calling Module:parameters.process()
	-- frame - the frame containing the arguments to be checked
	-- allowed_params - a table of valid arguments
	-- nocat - if specified, will not included category in warning_text
	-- noattn - if specified, will not include attention seeking span in in warning_text
	-- nowarn - if specified, will not include preview warning in warning_text
	-- returns valid_args, invalid_args, warning_text

	local valid_args, invalid_args = require("Module:parameters").process(frame.args, allowed_params, "return unknown")
    if next(invalid_args) then
	    local template_name = frame:getTitle()
    	return valid_args, invalid_args, make_warning_text(template_name, invalid_args, nowarn, noattn, nocat)
	end

    return valid_args, invalid_args, ""
end

function export.warn(frame)
	-- This is designed to be called by non-Lua templates using "{{#invoke:checkparams|warn}}"
    -- the passed frame is checked for the following params:
	-- 1= (optional) a comma separated list of allowed parameters
	--     if not specified, allows all parameters used as variables inside the template
	-- nowarn= (optional) do not include preview warning in warning_text
	-- noattn= (optional) do not include attention seeking span in in warning_text
	-- nocat= (optional) do not included category in warning_text

    local nowarn = frame.args["nocat"] or false
    local noattn = frame.args["noattn"] or false
    local nocat = frame.args["nocat"] or false

    local allowed_args = get_allowed_args(frame)
	local valid_args, invalid_args, warning_text = export.process(frame:getParent(), allowed_args, nowarn, noattn, nocat)
    return warning_text
end

function export.error(frame)
	-- This is designed to be called by non-Lua templates using "{{#invoke:checkparams|error}}"
    -- the passed frame is checked for the following params:
	-- 1= (optional) a comma separated list of allowed parameters
	--     if not specified, allows all parameters used as variables inside the template

    local allowed_args = get_allowed_args(frame)
	local valid_args, invalid_args = require("Module:parameters").process(frame:getParent().args, allowed_args, "return unknown")

    if next(invalid_args) then
		local invalid_args_string = params_to_string(invalid_args)
		local template_name = frame:getTitle()
		error("Invalid params in call to " .. template_name .. ": " .. invalid_args_string)
    end

end

return export