local split = require("Module:string utilities").split

local export = {}

-- Used in multiple places; create a variable for ease in testing.
local poscatboiler_template = "poscatboiler"
local find = mw.ustring.find
local match = mw.ustring.match
local sub = mw.ustring.sub

local labelPatterns = {
	"คำ.-", "เลข", "รูปผัน.-", "พาร์ทิซิเพิล.-", "อุปสรรค", "ปัจจัย", "อาคม", "หน่วยคำ.-", "อักษรย่อ.-",
	"เครื่องหมายวรรคตอน", "ตัวอักษร", "ตัวเลข", "พยางค์", "สัญลักษณ์", "วลี", "ประโยค", "ฮิรางานะ.-", "คาตากานะ.-",
	"ภาพพจน์", "สำนวน", "เฉิงยฺหวี่", "พาลินโดรม", "สแลง.-", "ชื่อ.-", "นามสกุล",
	"จำนวน.-", --เป็นได้ทั้ง POS และ topic
	"การ.-", "ภาคผนวก", "มอดูล.-", "แม่แบบ.-", "หน้าที่มีลิงก์แดง", "หน้าที่มีคำแปล", "รายการอรรถาภิธาน",
}

local function split_label_lang(titleObject)
	local getByCanonicalName = require("Module:languages").getByCanonicalName
	local canonicalName
	local label, lang
	local pattern
	
	--[=[
	-- Progressively add another word to the potential canonical name until it
	-- matches an actual canonical name.
	local words = split(titleObject.text, " ", true)
	for i = #words - 1, 1, -1 do
		canonicalName = table.concat(words, " ", 1, i)
		lang = getByCanonicalName(canonicalName)
		if lang then
			break
		end
	end
	
	local label = lang and sub(titleObject.text, #canonicalName + 2)
		or titleObject.text
	--]=]
	
	if find(titleObject.text, "^ศัพท์") then
		-- ตามลำดับ "แบ่ง" ขึ้นก่อนเพราะมีหมวดหมู่ "หน้าที่"
		canonicalName, label = match(titleObject.text, "^ศัพท์ภาษา(.+)(แบ่ง.+)$")
		if not canonicalName then
			canonicalName, label = match(titleObject.text, "^ศัพท์ภาษา(.+)(ที่.+)$")
		end
		if canonicalName then
			label = "ศัพท์" .. label
			lang = getByCanonicalName(canonicalName)
		else
			label = titleObject.text
			lang = nil
		end
	else
		for _,pattern in ipairs(labelPatterns) do
			label, canonicalName = match(titleObject.text, "^(" .. pattern .. ")ภาษา(.+)$")
			if canonicalName then
				canonicalName1, inScript = match(canonicalName, "^(.+)(ในอักษร.+)$")
				if canonicalName1 and inScript then
					canonicalName = canonicalName1
					label = label .. inScript
				end
				lang = getByCanonicalName(canonicalName)
				break
			end
		end
	end
	
	return label, lang
end


--[==[
Copy the arguments in `source` to those in `receiver`, offsetting numeric arguments by `offset`.

Handlers that invoke {{tl|poscatboiler}} use this to pass user-specified arguments to {{tl|poscatboiler}} along with
arguments specifying the category and its type. Specifically, {{tl|poscatboiler}} requires arguments specified in 1=
(the language name embedded in the category), 2= (the category name minus the language name and any suffixed script),
3= (the script code of categories of the form [[:Category:Pali nouns in Devanagari script]]) and raw= (true for raw
categories). User-specified numeric parameters are passed in 4= and above hence `offset` will normally be 3.
]==]
function export.copy_args(receiver, source, offset)
	for k, v in pairs(source) do
		if type(k) == "number" then
			receiver[k + offset] = v
		else
			receiver[k] = v
		end
	end
	return receiver
end


-- List of handler functions that try to match the page name. A handler should return a table of template title plus
-- arguments to be passed to frame:expandTemplate(). If a handler does not recognize the page name, it should return
-- nil. Note that the order of functions matters!

local handlers = {}

local function add_handler(func)
	table.insert(handlers, func)
end


-- ws topic cat
add_handler(function(titleObject)
	if not find(titleObject.text, "^อรรถาภิธาน:[a-z-]+:.") then
		return nil
	end
	
	local code, label = match(titleObject.text, "^อรรถาภิธาน:([a-z-]+):(.+)$")
	return {title = "ws topic cat", args = {code, label}}
end)


-- Topical categories
add_handler(function(titleObject)
	if not find(titleObject.text, "^[a-z-]+:.") then
		return nil
	end
	
	local code, label = match(titleObject.text, "^([a-z-]+):(.+)$")
	return {title = "topic cat", args = {code, label}}
end)


-- Fancy version of ine() (if-not-empty). Converts empty string to nil, but also strips leading/trailing space.
function export.ine(arg)
	if not arg then return nil end
	arg = mw.text.trim(arg)
	if arg == "" then return nil end
	return arg
end


-- Dialect categories e.g. for [[:Category:New Zealand English]] or [[:Category:Issime Walser]]
add_handler(function(titleObject, args)
	if export.ine(args.dialect) then
		local args = export.copy_args({nil, titleObject.text}, args, 3)
		args.raw = true
		return {
			title = poscatboiler_template,
			args = args,
		}, true
	end
end)


-- poscatboiler lang-specific
add_handler(function(titleObject, args)
	local label, lang = split_label_lang(titleObject)
	if lang then
		local baseLabel, script = match(label, "(.+)ในอักษร(.-)$")
		if script and baseLabel ~= "ศัพท์" then
			local scriptObj = require("Module:scripts").getByCanonicalName(script)
			if scriptObj then
				return {title = poscatboiler_template, args =
					export.copy_args({lang:getCode(), baseLabel, scriptObj:getCode()}, args, 3)}, true
			end
		end
		return {title = poscatboiler_template, args = export.copy_args({lang and lang:getCode() or "", label}, args, 3)}, true
	end
end)


-- poscatboiler umbrella category
add_handler(function(titleObject, args)
	local label = match(titleObject.text, "(.+)แบ่งตามภาษา$")
	if label then
		return {
			title = poscatboiler_template,
			-- The poscatboiler code will appropriately lowercase if needed.
			args = export.copy_args({nil, label}, args, 3)
		}, true
	end
end)


-- ws topic cat
add_handler(function(titleObject)
	if not find(titleObject.text, "^อรรถาภิธาน:") then
		return nil
	end

	return {title = "ws topic cat", args = {nil, string.sub(titleObject.text, 11)}}
end)


-- fixed กลุ่มภาษา... must come before topic cat
add_handler(function(titleObject, args)
	local args = export.copy_args({nil, titleObject.text}, args, 3)
	args.raw = true
	if match(titleObject.text, "^กลุ่มภาษา.+$") then
		return {
			title = poscatboiler_template,
			args = args,
		}, true
	end
end)


-- fixed ภาษา... must come before topic cat
add_handler(function(titleObject, args)
	local args = export.copy_args({nil, titleObject.text}, args, 3)
	args.raw = true
	if match(titleObject.text, "^ภาษา.+$") and titleObject.text ~= "ภาษา (หัวข้อ)" and titleObject.text ~= "ภาษาศาสตร์" then
		return {
			title = poscatboiler_template,
			args = args,
		}, true
	end
end)


-- topic cat
add_handler(function(titleObject)
	return {title = "topic cat", args = {nil, titleObject.text}}
end)


-- poscatboiler raw handlers
add_handler(function(titleObject, args)
	local args = export.copy_args({nil, titleObject.text}, args, 3)
	args.raw = true
	return {
		title = poscatboiler_template,
		args = args,
	}, true
end)


-- poscatboiler umbrella handlers without 'by language'
add_handler(function(titleObject, args)
	local args = export.copy_args({nil, titleObject.text}, args, 3)
	return {
		title = poscatboiler_template,
		args = args,
	}, true
end)


function export.show(frame)
	local args = frame:getParent().args
	local titleObject = mw.title.getCurrentTitle()
	
	if titleObject.nsText == "แม่แบบ" then
		return "(This template should be used on pages in the Category: namespace.)"
	elseif titleObject.nsText ~= "หมวดหมู่" then
		error("This template/module can only be used on pages in the Category: namespace.")
	end

	local function extra_args_error(templateObject)
		local numargstext = {}
		local argstext = {}
		local maxargnum = 0
		for k, v in pairs(templateObject.args) do
			if type(v) == "number" and v > maxargnum then
				maxargnum = v
			else
				table.insert(numargstext, "|" .. k .. "=" .. v)
			end
		end
		for i = 1, maxargnum do
			local v = templateObject.args[i]
			if v == nil then
				v = "(nil)"
			elseif v == true then
				v = "(true)"
			elseif v == false then
				v = "(false)"
			end
			table.insert(argstext, "|" .. v)
		end
		error("Extra arguments to {{auto cat}} not allowed for this category (recognized as {{[[Template:" ..
			templateObject.title .. "|" .. templateObject.title .. "]]" .. numargstext .. argstext .. "}}")
	end

	local first_error_templateObject, first_error_args_handled, first_error_cattext

	-- Go through each handler in turn. If a handler doesn't recognize the format of the
	-- category, it will return nil, and we will consider the next handler. Otherwise,
	-- it returns a template name and arguments to call it with, but even then, that template
	-- might return an error, and we need to consider the next handler. This happens,
	-- for example, with the category "CAT:Mato Grosso, Brazil", where "Mato" is the name of
	-- a language, so the handler for {{poscatboiler}} fires and tries to find a label
	-- "Grosso, Brazil". This throws an error, and previously, this blocked fruther handler
	-- consideration, but now we check for the error and continue checking handlers;
	-- eventually, {{topic cat}} will fire and correctly handle the category.
	for _, handler in ipairs(handlers) do
		local templateObject, args_handled = handler(titleObject, args)
		
		if templateObject then
			require("Module:debug").track("auto cat/" .. templateObject.title)
			local cattext = frame:expandTemplate(templateObject)
			-- FIXME! We check for specific text found in most or all error messages generated
			-- by category tree templates (in particular, the second piece of text below should be
			-- in all error messages generated when a given module doesn't recognize a category name).
			-- If this text ever changes in the source modules (e.g. [[Module:category tree]],
			-- it needs to be changed here as well.)
			if cattext:find("Category:Categories that are not defined in the category tree") or
				cattext:find("The automatically%-generated contents of this category has errors") then
				if not first_error_cattext then
					first_error_templateObject = templateObject
					first_error_args_handled = args_handled
					first_error_cattext = cattext
				end
			else
				if not args_handled and next(args) then
					extra_args_error(templateObject)
				end
				return cattext
			end
		end
	end
	
	if first_error_cattext then
		if not first_error_args_handled and next(args) then
			extra_args_error(first_error_templateObject)
		end
		return first_error_cattext
	end
	error("{{auto cat}} couldn't recognize format of category name")
end

-- test function for injecting title string
function export.test(title)
	local args = {}
	if type(title) == "table" then
		if type(title.args[1]) == "string" then
			args = title.args
		else
			args = title:getParent().args
		end
		title = args[1]
	end
	
	local titleObject = {}
	titleObject.text = title
	
	for _, handler in ipairs(handlers) do
		local t = handler(titleObject, args)
		
		if t then
			return t.title
		end
	end	
end

-- use ExpandTemplates to "transclude" this on any Category: namespace page
function export.testTransclusion(frame)
	local args = frame.args
	local title = args[1]
	
	local titleObject = {}
	titleObject.text = title
	
	for _, handler in ipairs(handlers) do
		local t = handler(titleObject, {})
		
		if t then
			return frame:expandTemplate(t)
		end
	end	
end

return export