This module needs documentation.
Please document this module by describing its purpose and usage on the documentation page.

local decl_data = {}

local sa_utils = require("Module:sa-utilities")
local SLP_to_IAST = require("Module:sa-utilities/translit/SLP1-to-IAST")
local IAST_to_SLP = require("Module:sa-utilities/translit/IAST-to-SLP1")

local match = mw.ustring.match

-- Make a detection function for ARGS that fetches the stem according to MATCH_RE (which is matched against
-- args.lemma and should have the stem in the first capture). ADDL_CONDITION is an optional function of one
-- argument (ARGS) that must return true for the detection to happen.
local function make_detect(match_re, addl_condition)
	return function(args)
		if addl_condition and not addl_condition(args) then
			return false
		end
		local stem = match(args.lemma, match_re)
		if stem then
			args.stem = stem
			return true
		else
			return false
		end
	end
end

-- Construct all or part of a given noun's declension. Each of SG, DU and PL is a table, whose keys are
-- as follows:
--
-- n = nominative
-- a = accusative
-- v = vocative
-- i = instrumental
-- d = dative
-- ab = ablative
-- g = genitive
-- l = locative
--
-- The corresponding value is one of the following:
-- 1. a "copy spec" such as "[ins]", meaning to copy from the instrumental of the same number;
-- 2. a single string (specifying an ending); or
-- 3. a list of specs, where a spec is either a string (an ending) or a table of the form
--    {"ENDING", stem = "STEM", mono = TRUE/FALSE, note = "NOTE"}. The latter format lets you explicitly specify what
--    the stem is, whether the form is monosyllabic, and what the footnote is. All named keys are optional.
--
-- In forms 2 and 3, if the ending begins with +, the stem defaults to args.lemma; otherwise it defaults to args.stem.
local function decline(args, data, sg, du, pl)
	local cases = {n="nom", a="acc", v="voc", i="ins", d="dat", ab="abl", g="gen", l="loc"}
	local function process_number(endings, tag)
		if not endings then
			return
		end
		for case, es in pairs(endings) do
			if type(es) == "string" and es:find("^%[") then
				-- copy from another case; skip and handle later
			else
				if type(es) == "string" then
					es = {es}
				end
				local forms = {}
				for i, e in ipairs(es) do
					local stem, mono, final, note
					if type(e) == "table" then
						stem = e.stem
						mono = e.mono
						final = e.final
						note = e.note
						e = e[1]
					end
					
					-- reduce Vedic forms to zero in case of novedic parameter
					if args.novedic == true and note and (match(note, "[vV]edic$") or match(note, "Br[aā]hma[nṇ]a")) then 
						stem = ""; e = ""; note = ""
					else
						if e:find("^%+") then
							if stem then
								error("Internal error: Can't use + in an ending when stem is explicitly given")
							end
							e = e:gsub("^%+", "")
							stem = args.lemma
						elseif not stem then
							stem = args.stem
						end

						forms[i] = sa_utils.internal_sandhi({
							stem = stem, ending = e, has_accent = args.has_accent, recessive = case == "v", 
							mono = mono, final = final, ambig_final = args.ambig_final, diaspirate = args.diaspirate,
						})
						if note and note~= "" then
							forms["note" .. i] = note
						end
					end
				end
				data.forms[cases[case] .. "_" .. tag] = forms
			end
		end

		-- Now handle cases copied from another.
		for case, es in pairs(endings) do
			if type(es) == "string" and es:find("^%[") then
				-- copy from another case; skip and handle later
				local other_case = es:match("^%[(.*)%]$")
				if not other_case then
					error("Internal error: Unrecognized copy case spec " .. es)
				end
				local other_slot = other_case .. "_" .. tag
				local value = data.forms[other_slot]
				if not value then
					error("Internal error: Slot '" .. other_slot .. "' to copy from is empty")
				end
				local this_slot = cases[case] .. "_" .. tag
				if data.forms[this_slot] then
					error("Internal error: A value already exists for slot '" .. this_slot ..
						"' when copying from '" .. other_slot .. "'")
				end
				data.forms[cases[case] .. "_" .. tag] = value
			end
		end
	end

	process_number(sg, "s")
	process_number(du, "d")
	process_number(pl, "p")
end


decl_data["a"] = {
	detect = make_detect("(.+)a" .. sa_utils.accent .. "?$",
		function(args) return args.g == "m" or args.g == "n" end)
}

setmetatable(decl_data["a"], {
	__call = function(self, args, data)
		local oxy = match(args.lemma, "(" .. sa_utils.accent .. "?)$")

		data.decl_type = "a-stem"

		if args.g == "m" then
			decline(args, data, {
				n="a" .. oxy .. "s",
				a="a" .. oxy .. "m",
				v="a"
			}, {
				n={ "O" .. oxy, { "A" .. oxy, note = "Vedic" } },
				a="[nom]",
				v={ "O", { "A", note = "Vedic" } }
			}, {
				n={"A" .. oxy .. "s", {"A" .. oxy .. "sas", note="Vedic"}},
				a="A" .. oxy .. "n",
				v={"As", {"Asas", note="Vedic"}}
			})
		else
			decline(args, data, {
				n="a" .. oxy .. "m",
				a="[nom]",
				v="a"
			}, {
				n="e" .. oxy,
				a="[nom]",
				v="e"
			}, {
				n={"A" .. oxy .. "ni", {"A" .. oxy, note="Vedic"}},
				a="[nom]",
				v={"Ani", {"A", note="Vedic"}}
			})
		end

		decline(args, data, {
			i="e" .. oxy .. "na",
			d="A" .. oxy .. "ya",
			ab="A" .. oxy .. "t",
			g="a" .. oxy .. "sya",
			l="e" .. oxy
		}, {
			i="A" .. oxy .. "ByAm",
			d="[ins]",
			ab="[ins]",
			g="a" .. oxy .. "yos",
			l="[gen]"
		}, {
			i={ "E" .. oxy .. "s", { "e" .. oxy .. "Bis", note = "Vedic" } },
			d="e" .. oxy .. "Byas", 
			ab="[dat]",
			g="A" .. oxy .. "nAm", 
			l="e" .. oxy .. "zu"
		})
		table.insert(data.categories, "Sanskrit a-stem nouns")
	end
})

decl_data["iu"] = {
	detect = make_detect("(.+)[iu]" .. sa_utils.accent .. "?$")
}

setmetatable(decl_data["iu"], {
	__call = function(self, args, data)
		local vowel, oxy = match(args.lemma, "([iu])(" .. sa_utils.accent .. "?)$")

		data.decl_type = vowel .. "-stem"

		if args.g == "m" then
			if vowel == "i" then
				decline(args, data, {
					d="a" .. oxy .. "ye",
					l={ "O" .. oxy, { "A" .. oxy, note = "Vedic"}}, -- Whitney §336f
				}, {}, {})
			elseif match(args.stem, sa_utils.consonant .. sa_utils.consonant .. "$") then
				-- the dative ending -ve (vs. -ave) does not occur if the stem has two consonants before -u
				decline(args, data, {
					d="a" .. oxy .. "ve",
					l="O" .. oxy,
				}, {}, {})
			else decline(args, data, {
					d={ "a" .. oxy .. "ve", {stem = args.stem .. vowel, "e" .. oxy, note = "Vedic"}},
					l="O" .. oxy,
				}, {}, {})
			end
			if match(args.stem, sa_utils.consonant .. sa_utils.consonant .. "$") then
				decline(args, data, {
					ab={{sa_utils.up_one_grade[vowel .. oxy] .. "s"}},
				}, {}, {})
			else
				decline(args, data, {
					ab={sa_utils.up_one_grade[vowel .. oxy] .. "s", {stem = args.stem .. vowel, "a" .. oxy .. "s", note = "Vedic"}},
				}, {}, {})
			end
			decline(args, data, {
				n="+s",
				a="+m",
				i={ "+nA", {stem = args.stem .. vowel, "A" .. oxy, note = "Vedic" } },
				g="[abl]",
				v=sa_utils.up_one_grade[vowel .. oxy],
			}, {
				n="+" .. vowel,
				a="[nom]",
				i="+ByAm",
				d="[ins]",
				ab="[ins]",
				g={{stem = args.stem .. vowel, "o" .. oxy .. "s"}},
				l="[gen]",
				v="+" .. vowel,
			}, {
				n={{stem = args.stem .. sa_utils.up_one_grade[vowel .. oxy], "as"}},
				a="+" .. vowel .. "n",
				i="+Bis",
				d="+Byas",
				ab="[dat]",
				g=sa_utils.lengthen[vowel] .. "nA" .. (oxy ~= "" and "/" or "") .. "m",
				l="+su",
				v={{stem = args.stem .. sa_utils.up_one_grade[vowel], "as"}},
			})
		elseif args.g == "f" then
			if vowel == "i" then
				decline(args, data, {
					i={{stem = args.stem .. vowel, "A" .. oxy}, {"+i", note = "Vedic"}},  -- Whitney §336c
					d={ "a" .. oxy .. "ye", {stem = args.stem .. vowel, "E" .. oxy, note = "Later Sanskrit" }, { "+i", note = "Vedic" } },
					l={ "O" .. oxy, {stem = args.stem .. vowel, "A" .. oxy .."m", note = "Later Sanskrit" }, { "A" .. oxy, note = "Vedic"} },
				}, {}, {})
			else
				decline(args, data, {
					i={{stem = args.stem .. vowel, "A" .. oxy }},
					d={ "a" .. oxy .. "ve", {stem = args.stem .. vowel, "E" .. oxy, note = "Later Sanskrit" } },
					l={ "O" .. oxy, {stem = args.stem .. vowel, "A" .. oxy .."m", note = "Later Sanskrit" } },
				}, {}, {})
			end
			decline(args, data, {
				n="+s",
				a="+m",
				ab={sa_utils.up_one_grade[vowel .. oxy] .. "s", {stem = args.stem .. vowel, "A" .. oxy .."s", note = "Later Sanskrit"}, 
					{stem = args.stem .. vowel, "E" .. oxy, note = "Brāhmaṇas"}	},
				g="[abl]",
				v=sa_utils.up_one_grade[vowel .. oxy],
			}, {
				n="+" .. vowel,
				a="[nom]",
				i="+ByAm",
				d="[ins]",
				ab="[ins]",
				g={{stem = args.stem .. vowel, "o" .. oxy .. "s"}},
				l="[gen]",
				v="+" .. vowel,
			}, {
				n={{stem = args.stem .. sa_utils.up_one_grade[vowel .. oxy], "as"}},
				a="+" .. vowel .. "s",
				i="+Bis",
				d="+Byas",
				ab="[dat]",
				g=sa_utils.lengthen[vowel] .. "nA" .. (oxy ~= "" and "/" or "") .. "m",
				l="+su",
				v={{stem = args.stem .. sa_utils.up_one_grade[vowel], "as"}},
			})
		else
			if vowel == "i" then
				decline(args, data, {
					d={ "+ne", { "a" .. oxy .. "ye", note = "Vedic" }},
					ab={ "+nas", { "e" .. oxy .. "s", note = "Vedic"}},
					l={ "+ni", {"O" .. oxy, note = "Vedic"}, {"A" .. oxy, note = "Vedic"}}, -- Whitney §336f + §340f
				}, {}, {})
			elseif match(args.stem, sa_utils.consonant .. sa_utils.consonant .. "$") then
				-- the dative ending -ve (vs. -ave) does not occur if the stem has two consonants before -u; similar for abl./gen. -vas (vs. -os)
				decline(args, data, {
					d={ "+ne", { "a" .. oxy .. "ve", note = "Vedic" }},
					ab={ "+nas", { "o" .. oxy .. "s", note = "Vedic"}},
					l={ "+ni", {"O" .. oxy, note = "Vedic"}},
				}, {}, {})
			else decline(args, data, {
					d={ "+ne", { "a" .. oxy .. "ve", note = "Vedic" }, {stem = args.stem .. vowel, "e" .. oxy, note = "Vedic"}},
					ab={ "+nas", { "o" .. oxy .. "s", note = "Vedic"}, {stem = args.stem .. vowel, "a" .. oxy .. "s", note = "Vedic"}},
					l={ "+ni", {"O" .. oxy, note = "Vedic"}},
				}, {}, {})
			end
			decline(args, data, {
				n="+",
				a="[nom]",
				i={ "+nA", {stem = args.stem .. vowel, "A" .. oxy, note = "Vedic" }},
				g="[abl]",
				v={ "+", sa_utils.up_one_grade[vowel .. oxy] },
			}, {
				n="+nI",
				a="[nom]",
				i="+ByAm",
				d="[ins]",
				ab="[ins]",
				g="+nos",
				l="[gen]",
				v="+nI",
			}, {
				n={ { stem = args.stem .. sa_utils.lengthen[vowel .. oxy], "ni" }, { "+", note = "Vedic"}, { "+" .. vowel, note = "Vedic"} },
				a="[nom]",
				i="+Bis",
				d="+Byas",
				ab="[dat]",
				g=sa_utils.lengthen[vowel] .. "nA" .. (oxy ~= "" and "/" or "") .. "m",
				l="+su",
				v={ { stem = args.stem .. sa_utils.lengthen[vowel], "ni" }, { "+", note = "Vedic"}, { "+" .. vowel, note = "Vedic"} },
			})
		end

		table.insert(data.categories, "Sanskrit " .. vowel .. "-stem nouns")
	end
})

decl_data["AIU"] = {
	detect = function(args)
		if make_detect("(.+)[AIU]" .. sa_utils.accent .. "?$")(args) then
			args.true_mono = sa_utils.is_monosyllabic(args.lemma)
			return true
		else
			return false
		end
	end
}

setmetatable(decl_data["AIU"], {
	__call = function(self, args, data)
		local vowel, oxy = match(args.lemma, "([AIU])(" .. sa_utils.accent .. "?)$")

		data.decl_type = SLP_to_IAST.tr(vowel) .. "-stem"

		if not (args.root or args.compound or args.true_mono) then -- derived stem
			if vowel == "A" then
				decline(args, data, {
					n="+",
					i={ "a" .. oxy .. "yA", { "+", note = "Vedic" } },
					d="+yE",
					ab={"+yAs", {"+yE", note = "Brāhmaṇas"}},
					g="[abl]",
					l="+yAm",
					v="e",
				}, {
					n="e" .. oxy,
					g="a" .. oxy .. "yos",
					v="e",
				}, {
					n="+s",
					v="+s",
				})
			else
				if vowel == "I" then
					decline(args,data, {n="+"})
				else
					decline(args,data, {n="+s"})
				end
				decline(args, data, {
					i={{stem = args.stem .. vowel, "A" .. oxy}},
					d={{stem = args.stem .. vowel, "E" .. oxy}},
					ab={{stem = args.stem .. vowel, "A" .. oxy .. "s"}, {stem = args.stem .. vowel, "E" .. oxy, note = "Brāhmaṇas"}},
					g="[abl]",
					l={{stem = args.stem .. vowel, "A" .. oxy .. "m"}},
					v=sa_utils.shorten[vowel],
				}, {
					 n={ "+O", { "+", note = "Vedic" } },
					 g={{stem = args.stem .. vowel, "o" .. oxy .. "s"}},
					 v={ "+O", { "+", note = "Vedic" } },
				}, {
					n={ "+as", { "+s", note = "Vedic" } },
					v={ "+as", { "+s", note = "Vedic" } },
				})
			end
			decline(args, data, {
				a="+m",
			}, {
				a="[nom]",
				i="+ByAm",
				d="[ins]",
				ab="[ins]",
				l="[gen]",
			}, {
				a="+s",
				i="+Bis",
				d="+Byas",
				ab="[dat]",
				g="+nAm",
				l="+su",
			})
		elseif vowel == "A" then
			decline(args, data, {
				n="+s",
				a="+m",
				i="+",
				d="e" .. oxy,
				ab="a" .. oxy .. "s",
				g="[abl]",
				l="i" .. oxy,
				v="+s",
			}, {
				n={ "O" .. oxy, { "A" .. oxy, note = "Vedic" } },
				a="[nom]",
				i="+ByAm",
				d="[ins]",
				ab="[ins]",
				g="o" .. oxy .. "s",
				l="[gen]",
				v={ "O", { "A", note = "Vedic" } }
			}, {
				n="+s",
				a={ "+s", { "a" .. oxy .. "s", note = "Perhaps" } },
				i="+Bis",
				d="+Byas",
				ab="[dat]",
				g={ "+" .. sa_utils.lengthen[vowel] .. "nAm", { "+" .. sa_utils.lengthen[vowel] .. "m", note = "Perhaps" } },
				l="+su",
				v="+s",
			})
		elseif args.compound then
			if match(args.stem, sa_utils.consonant .. sa_utils.consonant .. "$") then
				decline(args, data, {
					a={{"+am", mono = true}},
					i={{"+A", mono = true}},
					d={{"+e", mono = true}},
					ab={{"+as", mono = true}},
					l={{"+i", mono = true}},
				}, {
					n={{"+O", mono = true}},
					g={{"+os", mono = true}},
					v={{"+O", mono = true}},
				}, {
					n={{"+as", mono = true}},
					g={ { "+Am", mono = true }, "+nAm" },
					v={{"+as", mono = true}},
				})
			else
				decline(args, data, {
					a={ { "+am", mono = true }, "+am" },
					i={ { "+A", mono = true }, "+A" },
					d={ { "+e", mono = true }, "+e" },
					ab={ { "+as", mono = true }, "+as" },
					-- weird special case
					l={ { "+i", mono = true }, { stem = args.stem .. sa_utils.vowel_to_cons[vowel], "i" .. (oxy ~= "" and "\\" or "") } },
				}, {
					n={ { "+O", mono = true }, "+O" },
					g={ { "+os", mono = true }, "+os" },
					v={ { "+O", mono = true }, "+O" },
				}, {
					n={ { "+as", mono = true }, "+as" },
					g={ { "+Am", mono = true }, "+nAm", "+Am" },
					v={ { "+as", mono = true }, "+as" },
				})
			end
			decline(args, data, {
				n="+s",
				g="[abl]",
				v="+s",
			}, {
				a="[nom]",
				i="+ByAm",
				d="[ins]",
				ab="[ins]",
				l="[gen]",
			}, {
				a="[nom]",
				i="+Bis",
				d="+Byas",
				ab="[dat]",
				l="+su",
			})
		elseif args.true_mono then
			decline(args, data, {
				n="+s",
				a={ { "+am", mono = true}},
				i={ { "+A" .. oxy, mono = true}},
				d={ { "+e" .. oxy, mono = true }, { "+E" .. oxy, mono = true, note = "Later Sanskrit" } },
				ab={ { "+a" .. oxy .. "s", mono = true }, { "+A" .. oxy .. "s", mono = true, note = "Later Sanskrit" },
					{ "+E" .. oxy, mono = true, note = "Brāhmaṇas" } },
				g="[abl]",
				l={ { "+i" .. oxy, mono = true }, { "+A" .. oxy .. "m", mono = true, note = "Later Sanskrit" } },
				v="[nom]",
			}, {
				n={{"+O", mono = true}},
				a="[nom]",
				i={{"+ByA" .. oxy .. "m", mono = true}},
				d="[ins]",
				ab="[ins]",
				g={{"+o" .. oxy .. "s", mono = true}},
				l="[gen]",
				v="[nom]",
			}, {
				n={{"+as", mono = true}},
				a="[nom]",
				i={{"+Bi" .. oxy .. "s", mono = true}},
				d={{"+Bya" .. oxy .. "s", mono = true}},
				ab="[dat]",
				g={ { "+A" .. oxy .. "m", mono = true }, { "+nA" .. oxy .. "m", mono = true, note = "Later Sanskrit" } },
				l={{"+su" .. oxy, mono = true}},
				v="[nom]",
			})
		else -- polysyllabic stems (Whitney §355 ff.)
			decline(args, data, {
				n="+s",
				a="+am",
				i="+A",
				d="+e",
				ab="+as",
				g="[abl]",
				l="+i",
				v=sa_utils.shorten[vowel],
			}, {
				n="+A",
				a="[nom]",
				i="+ByAm",
				d="[ins]",
				ab="[ins]",
				g="+os",
				l="[gen]",
				v="+A",
			}, {
				n="+as",
				a="[nom]",
				i="+Bis",
				d="+Byas",
				ab="[dat]",
				g="+nAm",
				l="+su",
				v="+as",
			})
		end

		table.insert(data.categories, "Sanskrit " .. SLP_to_IAST.tr(vowel) .. "-stem nouns")
	end
})

decl_data["f"] = { -- actually for nouns in ṛ
	detect = make_detect("(.+)f" .. sa_utils.accent .. "?$")
}

setmetatable(decl_data["f"], { -- actually for nouns in ṛ
	__call = function(self, args, data)
		local oxy = match(args.lemma, "(" .. sa_utils.accent .. "?)$")

		data.decl_type = SLP_to_IAST.tr("f") .. "-stem"

		if args.g == "n" then
			decline(args, data, {
				n="f" .. oxy,
				a="[nom]",
				i="+nA",
				d="+ne",
				ab="+nas",
				l="+ni",
				v={ "f", "ar" },
			}, {
				n="+nI",
				g="+nos",
				v="+nI",
			}, {
				n="F" .. oxy .. "ni",
				a="[nom]",
				v="Fni",
			})
		else
            if not args.r_stem_a then
            	error('Please specify the length of the accusative singular vowel with r_stem_a = "a" or "ā".')
            else
            	args.r_stem_a = IAST_to_SLP.tr(args.r_stem_a)
            end
			decline(args, data, {
				n="A" .. oxy,
				a=args.r_stem_a .. oxy .. "ram",
				i="rA" .. oxy,
				d="re" .. oxy,
				ab="u" .. oxy .. "r",
				l="a" .. oxy .. "ri",
				v="ar",
			}, {
				n={ args.r_stem_a .. oxy .. "rO", { args.r_stem_a .. oxy .. "rA", note = "Vedic" } },
				g="ro" .. oxy .. "s",
				v={ args.r_stem_a .. "rO", { args.r_stem_a .. "rA", note = "Vedic" } },
			}, {
				n=args.r_stem_a .. oxy .. "ras",
				a=args.g == "f" and "F" .. oxy .. "s" or "F" .. oxy .. "n",
				v=args.r_stem_a .. "ras",
			})
		end
		decline(args, data, {
			g="[abl]",
		}, {
			a="[nom]",
			i="+ByAm",
			d="[ins]",
			ab="[ins]",
			l="[gen]",
		}, {
			i="+Bis",
			d="+Byas",
			ab="[dat]",
			g={{stem = args.stem .. "F", "nA" .. (oxy ~= "" and "/" or "") .. "m"}},
			l="+su",
		})

		table.insert(data.categories, "Sanskrit " .. SLP_to_IAST.tr("f") .. "-stem nouns")
	end
})

decl_data["[aiu]s"] = {
	detect = make_detect("(.+)[aiu]" .. sa_utils.accent .. "?s$")
}

setmetatable(decl_data["[aiu]s"], {
	__call = function(self, args, data)
		local vowel, oxy = match(args.lemma, "([aiu])(" .. sa_utils.accent .. "?)s$")

		data.decl_type = vowel .. "s-stem"

		if match(args.stem, "[^a/\\]"..sa_utils.accent.."?y$") and vowel == "a" and args.g == "m" then -- comparatives
			decline(args, data, {
				n="An",
				a="AMsam",
				v={"an", { "+", note = "Rigvedic"}},
			}, {
				n={"AMsO", {"AMsA", note = "Vedic"}},
				v={"AMsO", {"AMsA", note = "Vedic"}},
			}, {
				n="AMsas",
				a="+as",
				v="AMsas",
			})
		elseif args.g == "m" or args.g == "f" then
			if vowel == "a" then
				decline(args, data, {
					n="A" .. oxy .. "s",
					a={ "+am", { "A" .. oxy .. "m", note = "Vedic" } },
				}, nil, {
					n={ "+as", { "A" .. oxy .. "s", note = "Vedic" } },
					v={ "+as", { "A" .. oxy .. "s", note = "Vedic" } },
				})
			else
				decline(args, data, {
					n="+",
					a="+am",
				}, nil, {
					n="+as",
					v="+as",
				})
			end

			decline(args, data, {
				v="+",
			}, {
				n={ "+O", { "+A", note = "Vedic" } },
				v={ "+O", { "+A", note = "Vedic" } },
			}, {
				a="[nom]",
			})
		else
			decline(args, data, {
				n="+",
				a="[nom]",
				v="+",
			}, {
				n="+I",
				v="+I",
			}, {
				n={{stem = args.stem .. sa_utils.lengthen[vowel] .. oxy .. "Ms", "i"}},
				v={{stem = args.stem .. sa_utils.lengthen[vowel] .. "Ms", "i"}},
				a="[nom]",
			})
		end

		decline(args, data, {
			i="+A",
			d="+e",
			ab="+as",
			g="[abl]",
			l="+i",
		}, {
			a="[nom]",
			i= {{"+ByAm", final = true }},
			d="[ins]",
			ab="[ins]",
			g="+os",
			l="[gen]",
		}, {
			i= {{"+Bis", final = true }},
			d= {{"+Byas", final = true }},
			ab="[dat]",
			g="+Am",
			l= {{"+su", final = true }},
		})

		table.insert(data.categories, "Sanskrit " .. vowel .. "s-stem nouns")
	end
})

decl_data["an"] = {
	detect = make_detect("(.+)a" .. sa_utils.accent .. "?n$")
}

setmetatable(decl_data["an"], {
	__call = function(self, args, data)
		local oxy = match(args.lemma, "a(" .. sa_utils.accent .. "?)n$")

		data.decl_type = "an-stem"

		if not match(args.stem, sa_utils.consonant .. "[NYRnmyrlv]$") or args.contract then
			decline(args, data, {
				i="nA" .. oxy,
				d="ne" .. oxy,
				ab="na" .. oxy .. "s",
				g="[abl]",
				l={ "ni" .. oxy, "+i", { "+" , note = "Vedic"} }, -- Whitney §425c for Vedic form
			}, {
				g="no" .. oxy .. "s",
			})

			if args.g ~= "m" then
				decline(args, data, nil, {
					n={ "nI" .. oxy, "+I" },
					v={ "nI", "+I" },
				}, {
					a={ "A" .. oxy .. "ni", {"a" .. oxy, note = "Vedic"},  {"A" .. oxy, note = "Vedic"}},
				})
			else
				decline(args, data, nil, {
					n={ "A" .. oxy .. "nO", { "A" .. oxy .. "nA", note = "Vedic" } },
					v={ "AnO", { "AnA", note = "Vedic" } },
				}, {
					a="na" .. oxy .. "s",
				})
			end
			decline(args, data, nil, nil, {
				g="nA" .. oxy .. "m",
			})
		else
			decline(args, data, {
				i="+A",
				d="+e",
				ab="+as",
				g="[abl]",
				l= { "+i", { "+" , note = "Vedic"} }, -- Whitney §425c for Vedic form
			}, {
				g="+os",
			})

			if args.g ~= "m" then
				decline(args, data, nil, {
					n="+I",
					v="+I",
				}, {
					a={ "A" .. oxy .. "ni", {"a" .. oxy, note = "Vedic"},  {"A" .. oxy, note = "Vedic"}},
				})
			else
				decline(args, data, nil, {
					n={ "A" .. oxy .. "nO", { "A" .. oxy .. "nA", note = "Vedic" } },
					v={ "AnO", { "AnA", note = "Vedic" } },
				}, {
					a="+as",
				})
			end
			decline(args, data, nil, nil, {
				g="+Am",
			})
		end

		if args.g ~= "m" then
			decline(args, data, {
				n="a" .. oxy,
				a="[nom]",
				v={ "+", "a" },
			}, nil, {
				n={ "A" .. oxy .. "ni", {"a" .. oxy, note = "Vedic"},  {"A" .. oxy, note = "Vedic"}},
				v={ "Ani", {"a", note = "Vedic"},  {"A", note = "Vedic"}},
			})
		else
			decline(args, data, {
				n="A" .. oxy,
				a="A" .. oxy .. "nam",
				v="+",
			}, nil, {
				n="A" .. oxy .. "nas",
				v="Anas",
			})
		end

		decline(args, data, nil, {
			a="[nom]",
			i="a" .. oxy .. "ByAm",
			d="[ins]",
			ab="[ins]",
			l="[gen]",
		}, {
			i="a" .. oxy .. "Bis",
			d="a" .. oxy .. "Byas",
			ab="[dat]",
			l="a" .. oxy .. "su",
		})

		table.insert(data.categories, "Sanskrit an-stem nouns")
	end
})

decl_data["in"] = {
	detect = make_detect("(.+)i" .. sa_utils.accent .. "?n$")
}

setmetatable(decl_data["in"], {
	__call = function(self, args, data)
		local oxy = match(args.lemma, "i(" .. sa_utils.accent .. "?)n$")

		data.decl_type = "in-stem"

		if args.g ~= "n" then
			decline(args, data, {
				n="I" .. oxy,
				a="i" .. oxy .. "nam",
				v="+",
			}, {
				n={ "+O", { "+A", note = "Vedic" } },
				v={ "+O", { "+A", note = "Vedic" } },
			}, {
				n="+as",
				a="[nom]",
				v="+as",
			})
		else
			decline(args, data, {
				n="i" .. oxy,
				a="[nom]",
				v={ "i", "+" },
			}, {
				n="+I",
				v="+I",
			}, {
				n="I" .. oxy .. "ni",
				a="[nom]",
				v="Ini",
			})
		end

		decline(args, data, {
			i="+A",
			d="+e",
			ab="+as",
			g="[abl]",
			l="+i",
		}, {
			a="[nom]",
			i="i" .. oxy .. "ByAm",
			d="[ins]",
			ab="[ins]",
			g="+os",
			l="[gen]",
		}, {
			i="i" .. oxy .. "Bis",
			d="i" .. oxy .. "Byas",
			ab="[dat]",
			g="+Am",
			l="i" .. oxy .. "zu",
		})

		table.insert(data.categories, "Sanskrit in-stem nouns")
	end
})

decl_data["root"] = {			-- for root nouns ending on -gh/c/j/th/d/dh/p/bh/m/r/ṣ/ś/h + -ās/os
	detect = function(args)
		-- capture of vowel needed for accent
		if make_detect("(.*)" .. sa_utils.vowel .. sa_utils.accent .. "?r?[GcjTdDpBmrzSh]$")(args) 
			or make_detect("(.*)[Ao]" .. sa_utils.accent .. "?s$")(args)  then
			args.true_mono = sa_utils.is_monosyllabic(args.lemma)
			return true
		else
			return false
		end
	end
}

setmetatable(decl_data["root"], {
	__call = function(self, args, data)
		local vowel, oxy, extra, cons = match(args.lemma, "(" .. sa_utils.vowel .. ")(" .. sa_utils.accent .. "?)(r?)([GcjTdDpBmrzShs])$")
		if match(cons, "[jSh]") ~= nil and not args.ambig_final then
			error('Please specify the final consonant in the nominative singular with ambig_final = "k" or "ṭ".')
		elseif args.ambig_final then -- also for cases like sarágh > saráṭ or potentially anuṣṭúbh > anuṣṭúk
			args.ambig_final = IAST_to_SLP.tr(args.ambig_final)
		end
		local nasal = cons:gsub(".", sa_utils.homorganic_nasal)
		if args.diaspirate then args.diaspirate = true end
		
		data.decl_type = "root-stem"

		-- neuter nom. plural (Whitney §389c, although saying that these forms with infixed nasal are not attested for nouns)
		if args.g == "n" then
			if match(cons, "[mr]") == nil and extra == "" then
				decline(args, data, nil, nil, {
					n= {{stem = args.stem .. vowel .. oxy .. nasal .. cons, "i"}},
					v= {{stem = args.stem .. vowel .. nasal .. cons, "i"}},
				})
			else
				decline(args, data, nil, nil, {
					n= "+i",
					v= "+i",
				})
			end
		end
		
		if args.true_mono then -- monosyllabic stem
			if args.g ~= "n" then
				decline(args, data, {
					-- nom.sg. -s is an 'invisible' (historical) ending to provoke vowel lengthening in -ir/-ur stems, see Whitney §392
					-- this ending should be removed by internal_sandhi at an early stage
					n= {{"+s", mono = true }},  
					a="+am",
					v= {{"+s", mono = true }},
				}, {
					n={ "+O", { "+A", note = "Vedic" } },
					v={ "+O", { "+A", note = "Vedic" } },
				}, {
					n="+as",
					v="+as"
				})
			else
				decline(args, data, {
					n= "+",
					a="[nom]",
					v= "+"
				}, {
					n= {{"+I" .. oxy, mono = true }},
					v= "+I"
				}, { })
			end
			decline(args, data, {
				i= {{ "+A" .. oxy, mono = true }},
				d= {{ "+e" .. oxy, mono = true }},
				ab= {{ "+a" .. oxy .. "s", mono = true }},
				g="[abl]",
				l= {{ "+i" .. oxy, mono = true }},
			}, {
				a= "[nom]",
				i= {{ "+ByA" .. oxy .. "m", mono = true, final = true }},
				d="[ins]",
				ab="[ins]",
				g= {{ "+o" .. oxy .. "s", mono = true }},
				l="[gen]",
			}, {
				a= "[nom]",
				i= {{ "+Bi" .. oxy .. "s", mono = true, final = true }},
				d= {{ "+Bya" .. oxy .. "s", mono = true, final = true }},
				ab="[dat]",
				g= {{ "+A" .. oxy .. "m", mono = true }},
				l= {{ "+su" .. oxy, mono = true , final = true }},
			})
		
		else -- polysyllabic stem, no accent on ending		
			if args.g ~= "n" then
				decline(args, data, {
					n= "+",  
					a="+am",
					v= "+",
				}, {
					n={ "+O", { "+A", note = "Vedic" } },
					v={ "+O", { "+A", note = "Vedic" } },
				}, {
					n="+as",
					v="+as"
				})
			else
				decline(args, data, {
					n= "+",
					a="[nom]",
					v= "+"
				}, {
					n= "+I",
					v= "+I",
				}, { })
			end
			decline(args, data, {
				i="+A",
				d="+e",
				ab="+as",
				g="[abl]",
				l="+i",
			}, {
				a= "[nom]",
				i= {{ "+ByAm", final = true }},
				d="[ins]",
				ab="[ins]",
				g="+os",
				l="[gen]",
			}, {
				a= "[nom]",
				i= {{ "+Bis", final = true }},
				d= {{ "+Byas", final = true }},
				ab="[dat]",
				g= "+Am",
				l= {{ "+su", final = true }},
			})
		end
		
		table.insert(data.categories, "Sanskrit root-stem nouns")
	end
})

decl_data["[Aiuf]t"] = {
	detect = make_detect("(.+)[Aiuf]" .. sa_utils.accent .. "?t$")
}

setmetatable(decl_data["[Aiuf]t"], {
	__call = function(self, args, data)
		local vowel, oxy = match(args.lemma, "([Aiuf])(" .. sa_utils.accent .. "?)t$")
		
		data.decl_type = SLP_to_IAST.tr(vowel) .. "t-stem"

		if args.g ~= "n" then
			decline(args, data, {
				n= vowel .. oxy .. "t",
				a="+am",
				v= vowel .. "t",
			}, {
				n={ "+O", { "+A", note = "Vedic" } },
				v={ "+O", { "+A", note = "Vedic" } },
			}, {
				n="+as",
				a="[nom]",
				v="+as",
			})
		else
			decline(args, data, {
				n= vowel .. oxy .. "t",
				a="[nom]",
				v= vowel .. "t",
			}, {
				n="+I",
				v="+I",
			}, {
				n= vowel .. oxy .. "nti",
				a="[nom]",
				v= vowel .. "nti",
			})
		end

		decline(args, data, {
			i="+A",
			d="+e",
			ab="+as",
			g="[abl]",
			l="+i",
		}, {
			a="[nom]",
			i="+ByAm",
			d="[ins]",
			ab="[ins]",
			g="+os",
			l="[gen]",
		}, {
			i="+Bis",
			d="+Byas",
			ab="[dat]",
			g="+Am",
			l="+su",
		})

		table.insert(data.categories, "Sanskrit " .. SLP_to_IAST.tr(vowel) .. "t-stem nouns")
	end
})

decl_data["at"] = {
	detect = make_detect("(.+)a" .. sa_utils.accent .. "?t$")
}

setmetatable(decl_data["at"], {
	__call = function(self, args, data)
		local cons, oxy = match(args.lemma, "([mv]?)a(" .. sa_utils.accent .. "?)t$")
		
		-- for present participles
		if args.lemma:find("[vm]a/?t$") == nil or args.participle == true then
			data.decl_type = "at-stem"
			
			if args.g ~= "n" then
				decline(args, data, {
					n="a" .. oxy .. "n",
					a="a" .. oxy .. "ntam",
					v="an",
				}, {
					n={ "a" .. oxy .. "ntO", { "a" .. oxy .. "ntA", note = "Vedic"} },
					v={ "antO", { "antA", note = "Vedic"} },
				}, {
					n="a" .. oxy .. "ntas",
					a="ata" .. oxy .. "s",
					v="antas",
				})
			else
				decline(args, data, {
					n="a" .. oxy .. "t",
					a="[nom]",
					v="at",
				}, {
					n="a" .. oxy .. "ntI",
					v="antI",
				}, {
					n="a" .. oxy .. "nti",
					a="[nom]",
					v="anti",
				})
			end
			
			decline(args, data, {
				i="atA" .. oxy,
				d="ate" .. oxy,
				ab="ata" .. oxy .. "s",
				g="[abl]",
				l="ati" .. oxy,
			}, {
				a="[nom]",
				i="a" .. oxy .. "dByAm",
				d="[ins]",
				ab="[ins]",
				g="ato" .. oxy .. "s",
				l="[gen]",
			}, {
				i="a" .. oxy .. "dBis",
				d="a" .. oxy .. "dByas",
				ab="[dat]",
				g="atA" .. oxy .. "m",
				l="+su",
			})

			table.insert(data.categories, "Sanskrit at-stem nouns")
		
		-- for nouns suffixed with -vat/-mat
		else
			data.decl_type = cons .. "at-stem"
			
			if args.g ~= "n" then
				decline(args, data, {
					n="A" .. oxy .. "n",
					a="a" .. oxy .. "ntam",
					v={ "an", { "as", note = "Rigvedic"} },
				}, {
					n={ "a" .. oxy .. "ntO", { "a" .. oxy .. "ntA", note = "Vedic"} },
					v={ "antO", { "antA", note = "Vedic"} },
				}, {
					n="a" .. oxy .. "ntas",
					a="a" .. oxy .. "tas",
					v="antas",
				})
			else
				decline(args, data, {
					n="a" .. oxy .. "t",
					a="[nom]",
					v="at",
				}, {
					n="+I",
					v="+I",
				}, {
					n="a" .. oxy .. "nti",
					a="[nom]",
					v="anti",
				})
			end

			decline(args, data, {
				i="+A",
				d="+e",
				ab="+as",
				g="[abl]",
				l="+i",
			}, {
				a="[nom]",
				i="a" .. oxy .. "dByAm",
				d="[ins]",
				ab="[ins]",
				g="+os",
				l="[gen]",
			}, {
				i="a" .. oxy .. "dBis",
				d="a" .. oxy .. "dByas",
				ab="[dat]",
				g="+Am",
				l="+su",
			})

			table.insert(data.categories, "Sanskrit " .. cons .. "at-stem nouns")
		end
	end
})

decl_data["añc"] = {
	detect = make_detect("(.+[aA]" .. sa_utils.accent .. "?Yc)$",
		function(args) return args.g == "m" or args.g == "n" end)
}

setmetatable(decl_data["añc"], {
	__call = function(self, args, data)
		local cons, vowel, oxy = match(args.lemma, "([yv]?)([aA])(" .. sa_utils.accent .. "?)Yc$")
		
		data.decl_type = "añc-stem"
		
		-- make stem for 'middle' cases
		args.stem = args.stem:gsub("Yc$", "c")
		
		if args.g == "m" then
            decline(args, data, {
                n= "+",  -- args.lemma
                a="+am",
                v= "+",
			}, {
                n={ "+O", { "+A", note = "Vedic" } },
                v={ "+O", { "+A", note = "Vedic" } },
            }, {
                n="+as",
                v="+as",
            })
        else -- neuter
            decline(args, data, {
                n= "",  -- args.stem
                v= "",
                a="[nom]"
            }, {}, {
                n="+i",
                v="+i",
                a="[nom]"
            })
        end
		decline(args, data, {}, {
            i={{"ByAm", final = true}},
        }, {
            i={{"Bis", final = true}},
            d={{"Byas", final = true}},
            l={{"su", final = true}},
        })
        
		-- changing stem to weakest form
		if vowel == "A" then
            -- do nothing
        elseif match(args.stem, "tirya/?c$") then -- specifically for tiryañc
            args.stem = args.stem:gsub("ya(/?)c$", "a%1Sc")
        elseif cons ~= "" then
        	args.stem = args.stem:gsub("([yv])a([/\\]?)c$", 
        	function(cons, acc) return (cons == "y" and "I" or "U") .. (acc == "" and "" or "/") .. "c" end)
        elseif match(args.stem, "dac$") then
            args.stem = args.stem:gsub("ac$", "Ic")
        else
            error("Not supported")
        end
		
		if vowel == "A" or oxy == "" then
			if args.g == "m" then
				decline(args, data, nil, nil, {
					a="as",
				})
			else -- neuter
				decline(args, data, nil, {
					n="I",
					v="I",
				})
			end
			decline(args, data, {
				i="A",
				d="e",
				ab="as",
				l="i",
			}, {
				g="os",
			}, {
				g="Am",
			})
		else -- oxytone accent, showing two possible accentuations (Whitney §410)
            if args.g == "m" then
                decline(args, data, nil, nil, { a={"as", {"a" .. oxy .. "s", note = "Rigvedic", mono = true}} })
            else
                decline(args, data, nil, { 
                    n= {"I", {"I" .. oxy, note = "Rigvedic", mono = true}},
                    v= "I",
                })
            end
            decline(args, data, {
                i= {"A", {"A" .. oxy, note = "Rigvedic", mono = true}},
                d= {"e", {"e" .. oxy, note = "Rigvedic", mono = true}},
                ab= {"as", {"a" .. oxy .. "s", note = "Rigvedic", mono = true}},
                l= {"i", {"i" .. oxy, note = "Rigvedic", mono = true}},
            }, {
                g= {"os", {"o" .. oxy .. "s", note = "Rigvedic", mono = true}},
            }, {
                g= {"Am", {"A" .. oxy .. "m", note = "Rigvedic", mono = true}},
            })
        end
		
		decline(args, data, {
            g="[abl]"
        }, {
            a="[nom]",
            d="[ins]",
            ab="[ins]",
            l="[gen]",
        }, {
            ab="[dat]",
        })
        
		-- not adding noun class (for now?) as these are all adjectives
	end
})

decl_data["o"] = {
	detect = make_detect("(.+)o" .. sa_utils.accent .. "?$")
}

setmetatable(decl_data["o"], {
	__call = function(self, args, data)
		local oxy = match(args.lemma, "o(" .. sa_utils.accent .. "?)$")
		data.decl_type = "o-stem"

		if args.g ~= "n" or "f" or "m" then
			decline(args, data, {
				n="O" .. oxy .."s",
				a="A" .. oxy .."m",
				v="Os",
			}, {
				n={ "A" .. oxy .."vO", { "A" .. oxy .."vA", note = "Vedic" } },
				v={ "AvO", { "AvA", note = "Vedic" } },
			}, {
				n="A" .. oxy .."vas",
				a="A" .. oxy .."s",
				v="Avas",
			})
		end
		decline(args, data, {
			i="+A",
			d="+e",
			ab="o" .. oxy .. "s",
			g="[abl]",
			l="+i",
		}, {
			a="[nom]",
			i="+ByAm",
			d="[ins]",
			ab="[ins]",
			g= "+os",
			l="[gen]",
		}, {
			i="+Bis",
			d="+Byas",
			ab="[dat]",
			g="+Am",
			l="+su",
		})
		
		table.insert(data.categories, "Sanskrit o-stem nouns")
	end
})

decl_data["O"] = {
	detect = make_detect("(.+)O" .. sa_utils.accent .. "?$")
}

setmetatable(decl_data["O"], {
	__call = function(self, args, data)
		local oxy = match(args.lemma, "O(" .. sa_utils.accent .. "?)$")
		data.decl_type = "au-stem"

		if args.g ~= "n" or "f" or "m" then
			decline(args, data, {
				n="+s",
				a="+am",
				v="+s",
			}, {
				n={ "+O", { "+A", note = "Vedic" } },
				v={ "+O", { "+A", note = "Vedic" } },
			}, {
				n="+as",
				v="+as"
			})
		end

		decline(args, data, {
			i= {{ "+A" .. oxy, mono = true }},
			d= {{ "+e" .. oxy, mono = true }},
			ab={{ "+a" .. oxy .. "s", mono = true }},
			g="[abl]",
			l= {{ "+i" .. oxy, mono = true }},
		}, {
			a="[nom]",
			i= {{"+ByA" .. oxy .. "m", mono = true }},
			d="[ins]",
			ab="[ins]",
			g= {{ "+o" .. oxy .. "s", mono = true }},
			l="[gen]",
		}, {
			a="[nom]",
			i= {{"+Bi" .. oxy .. "s", mono = true }},
			d= {{"+Bya" .. oxy .. "s", mono = true }},
			ab="[dat]",
			g= {{ "+A" .. oxy .. "m", mono = true }},
			l= {{ "+su" .. oxy, mono = true }},
		})

		table.insert(data.categories, "Sanskrit au-stem nouns")
	end
})

decl_data["vāṃs"] = {
	detect = make_detect("(.*[^i])i?vA" .. sa_utils.accent .. "?Ms$",
		function(args) return args.g == "m" or args.g == "n" end)
}

setmetatable(decl_data["vāṃs"], {
	__call = function(self, args, data)
		local extra_i, oxy = match(args.lemma, "(i?)vA(" .. sa_utils.accent .. "?)Ms$")
		
		data.decl_type = "vāṃs-stem"

		if args.g == "m" then
			decline(args, data, {
				n= extra_i .. "vA" .. oxy .. "n",
				a= extra_i .. "vA" .. oxy .. "Msam",
				v={ extra_i .. "van", { extra_i .. "vas", note = "Rigvedic"} },
			}, {
				n= extra_i .. "vA" .. oxy .. "MsO",
				a="[nom]",
				v= extra_i .. "vAMsO",
			}, {
				n= extra_i .. "vA" .. oxy .. "Msas",
				a="u" .. oxy .. "zas",
				v= extra_i .. "vAMsas",
			})
		else
			decline(args, data, {
				n= extra_i .. "va" .. oxy .. "t",
				a="[nom]",
				v= extra_i .. "vat"
			}, {
				n="u" .. oxy .. "zI",
				a="[nom]",
				v="uzI",
			}, {
				n= extra_i .. "vA" .. oxy .. "MsI",
				a="[nom]",
				v= extra_i .. "vAMsI"
			})
		end

		decline(args, data, {
			i="u" .. oxy .. "zA",
			d="u" .. oxy .. "ze",
			ab="u" .. oxy .. "zas",
			g="u" .. oxy .. "zas",
			l="u" .. oxy .. "zi"
		}, {
			i= extra_i .. "va" .. oxy .. "dByAm",
			d="[ins]",
			ab="[ins]",
			g="u" .. oxy .. "zos",
			l="[gen]"
		}, {
			i= extra_i .. "va" .. oxy .. "dBis",
			d= extra_i .. "va" .. oxy .. "dByas", 
			ab="u" .. oxy .. "zAm",
			g="u" .. oxy .. "zAm", 
			l= extra_i .. "va" .. oxy .. "tsu"
		})
		table.insert(data.categories, "Sanskrit vāṃs-stem nouns")
	end
})

return decl_data