Module:sk-noun

From Wiktionary, the free dictionary
Jump to navigation Jump to search


--[=[
    This module contains functions for creating inflection tables for Slovak
    nouns.
]=]--

local export = {}

-- Within this module, declensions are the functions that do the actual
-- declension by creating the forms of a basic noun.
-- They are defined further down.
--local lemma = "Bratislava"
local declensions = {}
local lang = require("Module:languages").getByCode("sk")
local m_table = require("Module:table")
local m_links = require("Module:links")
local m_adj = require("Module:sk-adjective")

local allowed_genders = {
	"m-pr", "m-anml", "m-in", "f", "n"
}
local allowed_genders_set = m_table.listToSet(allowed_genders)

local hard_consonants = {"d", "t", "n", "l", "h", "ch", "k", "g"}
local soft_consonants = {"ď", "ť", "ň", "ľ", "ž", "š", "č", "dž", "c", "dz", "j"}
local ambiguous_consonants = {"b", "m", "p", "r", "s", "v", "z", "f"}

local hard_consonants_set = m_table.listToSet(hard_consonants)
local soft_consonants_set = m_table.listToSet(soft_consonants)
local ambiguous_consonants_set = m_table.listToSet(ambiguous_consonants)

local consonants = m_table.append(hard_consonants, soft_consonants, ambiguous_consonants)
local consonants_set = m_table.listToSet(consonants)

local short_vowels = {"a", "e", "i", "o", "u", "y", "ä", "ö", "ü"}
local long_vowels = {"á", "é", "í", "ó", "ú", "ý", "ő", "ű"}
local diphthongs = {"ia", "ie", "iu", "ô"}

local short_vowels_set = m_table.listToSet(short_vowels)
local long_vowels_set = m_table.listToSet(long_vowels)
local diphthongs_set = m_table.listToSet(diphthongs)

local vowels = m_table.append(short_vowels, long_vowels)
local vowels_set = m_table.listToSet(vowels)

local obstruents = {"p", "t", "k", "b", "d", "g", "f", "s", "š", "ch", "z", "ž", "č", "dž"} --"v" removed??
local sonorants = {"m", "n", "l", "r", "ʋ", "v"} --"v" added??

local obstruents_set = m_table.listToSet(obstruents)
local sonorants_set = m_table.listToSet(sonorants)

local bigraphs = {"ch", "dz", "dž"}
local bigraphs_set = m_table.listToSet(bigraphs)

local prepositions = {"bez", "bezo", "cez", "cezo", "do", "k", "ku", "medzi",
	"na", "nad", "nado", "o", "do",	"okrem", "po", "pod", "podo", "pre", "pred",
	"predo", "pri", "proti", "naproti", "oproti", "s", "so", "skrz", "u", "v",
	"vo", "z", "zo", "za"}  -- Add more prepositions as needed
local prepositions_set = m_table.listToSet(prepositions)
local conjunctions = {"a"}  -- Add more conjunctions as needed
local conjunctions_set = m_table.listToSet(conjunctions)

-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
    local args = frame:getParent().args
    NAMESPACE = mw.title.getCurrentTitle().nsText
    if NAMESPACE == "" then
    	PAGENAME = mw.title.getCurrentTitle().text
    else
    	if args["pagename"] then
    		PAGENAME = args["pagename"] -- TEST: lemma
    	else
    		error("Pagename not specified")
    	end
    end
    
    GENDER = check_gender(args[1])
    NUMBER = args["n"]
    TYPE = args["t"]
    GEN_ENDING = args["g"]
    PLURAL = args["pl"]
    PLURAL2 = args["pl2"]
    local genitive = args[2]
    local category = ""
 
    -- Check if PAGENAME is a multiword expression
    if not mw.ustring.find(PAGENAME, " ") then
        -- Single-word expression, handle as usual
        PATTERN = determine_pattern(PAGENAME, genitive)
        
        -- Category
	    if NAMESPACE == "" then
		    if PATTERN == "indeclinable" then
		    	category = "[[Category:Slovak indeclinable nouns|" .. PAGENAME .. "]]"
		    elseif PATTERN == "irregular" then
		    	category = "[[Category:Slovak irregular nouns|" .. PAGENAME .. "]]"
		    elseif PATTERN == "adjective" then
		    	category = "[[Category:Slovak adjectival nouns|" .. PAGENAME .. "]]"
		    else
		    	category = "[[Category:Slovak terms with declension " .. PATTERN .. "|" .. PAGENAME .. "]]"
		    end
		    
		    if GENDER == "m-anml" then
		    	category = category .. "[[Category:Slovak terms with declension chlap|" .. PAGENAME .. "]]"
		    end
		    
		    if PAGENAME == "cól" then
		    	category = category .. "[[Category:Slovak terms with declension dub|" .. PAGENAME .. "]]"
		    end
	    end
        
        local forms, title 
        
    	-- Find out if there are more genitive stems and if so, process them
        if genitive then
        	if mw.ustring.find(genitive, "/") then
				forms, title = decline_with_more_stems(genitive)
        	else
        		forms, title = declensions[PATTERN](PAGENAME, genitive)
        		normalize_forms(forms)
        	end
        else
	        forms, title = declensions[PATTERN](PAGENAME, genitive)
	        normalize_forms(forms)
	    end
        
        specified_by_user(forms, args)
        
        return make_table(forms, title) .. category
    end
    
    -- Multiword expressions
    -- Category
    if NAMESPACE == "" then
	    category = "[[Category:Slovak multiword terms|" .. PAGENAME .. "]]"
    end

    -- Split into words
    local units, gen_units = split_into_units(PAGENAME, genitive)

    -- Process each unit to generate forms
    local combined_forms = generate_combined_forms(units, gen_units)
    
    -- Add forms specified by the user
    specified_by_user(combined_forms, args)

    -- Generate the final title and table
    local title = "Declension of ''" .. PAGENAME .. "'' (multiword term)"
    return make_table(combined_forms, title) .. category
end

--[=[
    Declension functions
]=]--

declensions["chlap"] = function(word, genitive)
    local forms = {}
    local syllable_count, syllables = split_into_syllables(word)
    local mobile_removed = false  -- Tracks if a mobile vowel was removed
    local words_with_mobile = {"kmotor", "švagor", "svokor", "lotor",
    	"obor", "Uhor", "pochábeľ", "väzeň", "učeň",
    	"posol", "diabol", "blázon", "Turek", "fanúšek", "center",
    	"hypochonder", "Kafer", "neger", "gangster",
    	"kapor", "zubor", "bobor", "orol", "pes"
    }
    
    -- Determine the stem by removing mobile vowels if applicable
    local stem = word
    if (matches_any(word, words_with_mobile)
    	or ends_with_any(word, {"ec", "ček", "íček", "ok"}))
    	and not (TYPE == "surn"
    		and (ends_with_any(word, {"[aeiouyáéíóúýäöüőű]ček", "[aeiouyáéíóúýäöüőű]čok"})
    			or syllable_count == 1))
    	and not matches_any(word, {"otrok"})
    then
    	stem = remove_mobile_vowel(word)
        mobile_removed = true  -- Mark mobile vowel as removed
    end

    -- Adjust specific stems based on predefined mappings
    stem = ({["švec"] = "ševc", ["žnec"] = "ženc"})[word] or stem
    
    -- Remove certain endings for words ending with "us", "os", "es" and "o"
    if ends_with(word, "[uoea]s") then 
        stem = mw.ustring.sub(word, 1, -3)
    end
    if ends_with(word, "o") then 
        stem = remove_last_char(word)
    end
    
    -- Override if a specific argument is given
    if genitive and NUMBER ~= "pl" then 
        stem = remove_last_char(genitive)
    end

    -- Title for the declension table
    local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("chlap") .. ")"

    -- Helper variables for analyzing the last and penultimate characters
    local ultimate, penultimate = get_last_letters(stem)
    local first = get_first_char(word)

    -- Determine alternative dative and locative for some nouns
    if matches_any(word, {"človek", "pán", "čert", "diabol", "vlk", "pes",
    	"Boh", "boh", "otec", "syn", "duch", "Kristus", "don", "šor"})
    then
        set_forms(forms, {"dat_sg2", "loc_sg2"}, {stem .. "u", stem .. "u"})
    end
    
    local nom_pl = "i"
    if PLURAL then
    	if mw.ustring.find(PLURAL, "/") then
    		local endings = {}
			for part in mw.ustring.gmatch(PLURAL, "[^/]+") do
			    table.insert(endings, part)
			end
			nom_pl = endings[1]
			for i = 2, 4 do
				if endings[i] then
					set_forms(forms, {"nom_pl" .. i}, {append_ending(stem, endings[i])})
				else
					break
				end
			end
		else
			nom_pl = PLURAL
    	end
    elseif (ends_with_any(word, {"fil", "fób", "graf", "nóm"})
    	and not matches_any(word, {"xylograf", "typograf"}))
    	or matches_any(word, {"cynik", "epik", "klasik", "stoik",
    		"Brit", "Búr", "Gal", "Ír", "Dór", "Italik", "Ión", "Nór",
    		"cár", "lodivod", "odkundes", "starík", "muž", "svat", "apoštol",
    		"augur", "bán", "bogomil", "buržuj", "don", "druid", "eféb",
    		"famulus", "héros", "man", "posol"
    	})
	then
		set_forms(forms, {"nom_pl2"}, {append_ending(stem, "ovia")})
    elseif (ends_with_any(word, {"[^c]h", "g", "ček", "ek", "ik", "čik", "ok", "o", "us", "es", "duch"}) and word ~= "pes")
    	or matches_any(word, {"otec", "syn", "ujec", "svák", "svokor", "tesť", "švagor",
    		"zať", "kmotor", "ded", "kmeť", "pobratim", "otčim", "zlosyn",
    		"Bask", "Frank", "Ind", "Ink", "Kurd", "Dák", "Čudo", "Fríz", "Azték",
    		"Asýr", "Etrusk", "Kazach", "Kašub", "odľud", "člen", "zved", "hňup", "cicerone"})
    	or (ends_with(word, "ch") and TYPE == "loan")
    	or matches_any(TYPE, {"comp-deverb", "surn", "name"})
    	or PLURAL == "ovia"
    then
    	nom_pl = "ovia"
    	
    	if TYPE == "surn" then
    		append_second_plural(forms, stem .. "ovci", stem .. "ovcov", stem .. "ovcom",
    			stem .. "ovcov", stem .. "ovcoch", stem .. "ovcami")
    	end
    elseif ends_with(word, "teľ")
    	or (ends_with(word, "an") and is_capital(first))
    	or matches_any(word, {"občan", "dedinčan", "mešťan", "zeman", "ostrovan",
    		"krajan", "dolňan", "horňan", "rodič", "dedič", "brat", "hosť",
    		"manžel", "sused", "žid", "Žid", "kresťan", "pohan"})
    then
    	nom_pl = "ia"
    	
    	if word == "pohan" then
    		set_forms(forms, {"nom_pl2"}, {append_ending(stem, "i")})
    	end
    end

    -- Determine instrumental plural ending
    local ins_pl = "mi"  -- Default instrumental plural ending is "mi"
    if ultimate == "m"
        or mobile_removed
        or ends_with_any(word, {"o", "ius"})
        or matches_any(word, {"hosť", "tesť", "Kopt"}) --obstruents_set[penultimate] and obstruents_set[ultimate]
    then
        ins_pl = "ami"
    end

    -- Populate forms table with singular and plural endings
    append_endings(forms, word, stem,
        "a", "ovi", "a", "ovi", "om",
        nom_pl, append_ending(stem, "ov"), "om", "ov", "och", ins_pl
    )
    
    -- Exceptions
    if word == "človek" then
    	set_forms(forms,
    		{"nom_pl", "gen_pl", "dat_pl", "acc_pl", "loc_pl", "ins_pl"},
    		{"ľudia", "ľudí", "ľuďom", "ľudí", "ľuďoch", "ľuďmi"}
    	)
    elseif word == "héros" then
    	set_forms(forms,
    		{"gen_sg", "dat_sg", "acc_sg", "loc_sg", "ins_sg",
    			"nom_pl", "gen_pl", "dat_pl", "acc_pl", "loc_pl", "ins_pl",
    			"gen_sg2", "dat_sg2", "acc_sg2", "loc_sg2", "ins_sg2",
    			"nom_pl2", "gen_pl2", "dat_pl2", "acc_pl2", "loc_pl2", "ins_pl2"},
    		{"hérosa", "hérosovi", "hérosa", "hérosovi", "hérosom",
    			"hérosi", "hérosov", "hérosom", "hérosov", "hérosoch", "hérosmi",
    			"héroa", "héroovi", "héroa", "héroovi", "héroom",
    			"héroovia", "héroov", "héroom", "héroov", "hérooch", "héroami"}
    	)
    end
    
    -- Vocative
    if matches_any(word, {"syn", "sváko", "švagor", "kmotor", "brat", "synko",
    	"synáčik", "pán", "chlapec", "priateľ", "majster", "Boh", "boh", "Pán", "Syn",
    	"Duch", "Ježiš", "Ježiško", "Kristus", "Spasiteľ", "Vykupiteľ", "Stvoriteľ",
    	"Premožiteľ", "Kráľ", "Hospodin", "baránok", "Baránok", "tešiteľ", "Tešiteľ",
    	"hriešnik", "protivník", "kresťan", "otec", "Otec", "chlap", "človek"})
    then
    	voc_stem = stem
    	if word == "synáčik" then stem = "synáčk" end
    	
    	if ends_with_any(word, {"teľ", "o"})
    		or matches_any(word, {"syn", "Syn", "Duch", "Ježiš", "Kráľ",
    			"hriešnik", "synáčik", "baránok", "Baránok"})
    	then
    		set_forms(forms, {"voc_sg"}, {append_ending(voc_stem, "u")})
    	else
    		if ends_with_any(voc_stem, {"k", "c"}) then
    			voc_stem = remove_last_char(voc_stem) .. "č"
    		elseif ends_with(voc_stem, "h") then
    			voc_stem = remove_last_char(voc_stem) .. "ž"
    		end
    		
    		set_forms(forms, {"voc_sg"}, {append_ending(voc_stem, "e")})
    	end
    	
    	if not matches_any(word, {"človek", "pán"}) then
    		set_forms(forms, {"voc_sg2"}, {word})
    	end
    end

    return forms, title  -- Return forms table and title for declension
end

declensions["hrdina"] = function(word, genitive)
    local forms = {}
    local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("hrdina") .. ")"
    
    local stem = remove_suffix(word, "a")
    
    local nom_pl_ending = "ovia"
    if ends_with_any(word, {"ista", "ita"})
    	or (ends_with(word, "ta") and TYPE == "loan") then
    	nom_pl_ending = "i"
    end
    
    local ins_pl_ending = "ami"
    if ends_with(word, "[aeiouyáéíóúýäöüőű]ta") and TYPE == "loan" then
    	ins_pl_ending = "i"
    end
    
    append_endings(forms, word, stem,
    	"u", "ovi", "u", "ovi", "om",
    	nom_pl_ending, stem .. "ov", "om", "ov", "och", ins_pl_ending
    )
    
    if word == "buržoa" then
    	set_forms(forms,
    		{"dat_sg", "loc_sg", "ins_sg",
    			"nom_pl", "gen_pl", "dat_pl", "acc_pl", "loc_pl"},
    		{"buržuovi", "buržuovi", "buržuom",
    			"buržuovia", "buržuov", "buržuom", "buržuov", "buržuoch"}
    	)
    end
    
    return forms, title
end

declensions["kuli"] = function(word, genitive)
    local forms = {}
	local stem = word
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("kuli") .. ")"
	
    append_endings(forms, word, stem,
    	"ho", "mu", "ho", "m", "m",
    	"ovia", stem .. "ov", "om", "ov", "och", "ami"
    )
 
    return forms, title
end

declensions["dub"] = function(word, genitive)
    local forms = {}
    local mobile_removed = false  -- Tracks if a mobile vowel was removed
    local words_with_mobile = {"priemysel", "počet", "ocot", "bahor", "chrbát",
    	"ker", "cukor", "uhor", "vietor", "víchor", "vôdor", "kotol",
    	"kapor", "zubor", "bobor", "orol", "pes"}
    
    local syllable_count, syllables = split_into_syllables(word)
    
    -- Determine the stem by removing mobile vowels if applicable
    local stem = word
    if (matches_any(word, words_with_mobile)
    	or (ends_with_any(word, {"íček", "ýček", "ok"})) and syllable_count ~= 1)
    	and not matches_any(word, {"polrok", "nárok", "zákrok", "rozkrok"})
    then
    	stem = remove_mobile_vowel(word)
        mobile_removed = true  -- Mark mobile vowel as removed
    end

    -- Adjust specific stems based on predefined mappings
    stem = ({["mráz"] = "mraz", ["vietor"] = "vetr", ["kôl"] = "kol",
             ["stôl"] = "stol", ["vôl"] = "vol", ["chlieb"] = "chleb",
             ["ensemble"] = "ensembl"})[word] or stem
    
    -- Remove certain endings for words ending with "us", "os", "es" and "o"
    if (ends_with(word, "[uoe]s") and TYPE == "loan") or ends_with(word, "izmus") then 
        stem = mw.ustring.sub(word, 1, -3)
    end
    if ends_with(word, "o") then 
        stem = remove_last_char(word)
    end
    
    -- Pluralia tantum
    if NUMBER == "pl" then
    	stem = remove_suffix(word, "y")
    end
    
    -- Override if a specific argument is given
    if genitive and NUMBER ~= "pl" then 
        stem = remove_last_char(genitive)
        
        if stem == word then
        	mobile_removed = false
        end
    end

    -- Set "mobile_removed" if genitive was set by the user
    if ends_with(word, "e[lr]") and ends_with(stem, "[^e][lr]") then
        mobile_removed = true
    end

    -- Title for the declension table
    local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("dub") .. ")"
    if GENDER == "m-anml" then
    	if matches_any(word, {"vták", "vlk", "pes"}) then
    		title = "Declension of ''" .. word .. "'' (patterns " .. pattern_link("chlap") .. " <small>(singular, plural 1)</small> and " .. pattern_link("dub") .. " <small>(plural 2)</small>)"
    	elseif PLURAL2 then
    		title = "Declension of ''" .. word .. "'' (patterns " .. pattern_link("chlap") .. " <small>(singular, plural 2)</small> and " .. pattern_link("dub") .. " <small>(plural 1)</small>)"
    	else
    		title = "Declension of ''" .. word .. "'' (patterns " .. pattern_link("chlap") .. " <small>(singular)</small> and " .. pattern_link("dub") .. " <small>(plural)</small>)"
    	end
    end

    -- Helper variables for analyzing the last and penultimate characters
    local ultimate, penultimate = get_last_letters(stem)
    local first = get_first_char(word)

    -- Determine genitive singular ending
    local gen_sg = "a"
    if (matches_any(TYPE, {"abstr", "comp-deverb", "material", "collect", "loan"})
        or matches_any(GEN_ENDING, {"u", "u/a"})
        or ends_with_any(word, {"izmus", "x"})
        or (ends_with(word, "m") and not is_capital(first))
        or (ends_with(word, "z") and is_capital(first)))
        and not matches_any(GEN_ENDING, {"a", "a/u"})
    then
        gen_sg = "u"
    end
    
    -- Override genitive singular ending if a specific argument is given
    if genitive then 
        gen_sg = get_last_char(genitive)
    end

    -- Determine locative singular ending based on specific suffixes and patterns
    local loc_sg = "e"
    if ends_with_any(word, {"k", "g", "h", "ch", "ius", "eus"}) then
        loc_sg = "u"
    elseif (ends_with(word, "ér")
            or (ends_with(word, "ál") and not matches_any(word, {"bál", "paškál", "paušál", "šál", "škandál", "žurnál"}))
            or matches_any(word, {"Alžír", "klavír", "apríl", "jún", "júl"}))
    then
        loc_sg = "i"
    end

    -- Determine genitive plural ending
    local gen_pl = stem .. "ov"  -- Default genitive plural ending is "ov"
    
    -- Adjust genitive plural for specific place names and patterns
    if is_capital(first) and (ends_with_any(word, {"any", "íky", "áky", "iaky"})
        or matches_any(word, {"Krompachy", "Kubachy", "Veľbachy", "Spišské Vlachy",
            "Žabokreky", "Čechy", "Brezolupy", "Bruty", "Rakoľuby", 
            "Rakúsy", "Prusy", "Veľaty", "Piešťany", "Bieščary"}))
    then
        local gen_stem = remove_last_char(word)  -- Remove last character to get the base stem
        local syllable_count, syllables = split_into_syllables(gen_stem)
        local last_syllable = syllables[1]
        local penultimate_syllable = syllables[2] or ""
        
        -- Lengthen the vowel if the penultimate syllable is short, otherwise use unchanged stem
        if is_long(penultimate_syllable) then
            gen_pl = gen_stem
        else
            gen_pl = remove_suffix(gen_stem, last_syllable) .. lengthen_vowel(last_syllable)
        end
    end

    -- Special cases for genitive plural of specific words
    if word == "čas" then
        gen_pl = "čias"
    elseif word == "raz" then
        gen_pl = "ráz"
    end

    -- Determine instrumental plural ending
    local ins_pl = "mi"  -- Default instrumental plural ending is "mi"
    if ultimate == "m"
        or mobile_removed
        or ends_with_any(word, {"o", "ius", "eus"})
        or (penultimate == "m" and consonants_set[ultimate])
    then
        ins_pl = "ami"
    end
    
    -- Doublets for instrumental plural
    if matches_any(word, {"schod", "schody", "fúz", "fúzy", "roh", "rohy",
    	"paroh", "parohy", "čary", "orech"})
    then
    	set_forms(forms, {"ins_pl2"}, {stem .. "ami"})
    elseif matches_any(word, {"zub", "dol", "most", "prst", "piest", "necht"}) then
    	ins_pl = "ami"
    	set_forms(forms, {"ins_pl2"}, {stem .. "mi"})
    end

    -- Populate forms table with singular and plural endings
    append_endings(forms, word, stem,
        gen_sg, "u", nil, loc_sg, "om",
        "y", gen_pl, "om", nil, "och", ins_pl
    )
    
    -- Alternative genitive ending
    if GEN_ENDING == "a/u" then
    	set_forms(forms, {"gen_sg2"}, {stem .. "u"})
    elseif GEN_ENDING == "u/a" then
    	set_forms(forms, {"gen_sg2"}, {stem .. "a"})
    end
    
    -- Exceptions
    if matches_any(word, {"sen", "dol"}) then
    	set_forms(forms, {"loc_pl2"}, {stem .. "ách"})
    elseif word == "čas" then
    	set_forms(forms, {"gen_pl2"}, {"časov"})
    end
    
    -- Animal nouns
    if GENDER == "m-anml" then
    	append_animal_singular(forms, stem)
    	
    	if matches_any(word, {"vták", "vlk", "pes"}) or PLURAL2 then
    		local forms2, title2 = declensions["chlap"](word, stem .. gen_sg)
    		append_second_plural(forms, forms2["nom_pl"], forms2["gen_pl"], forms2["dat_pl"],
    			forms2["acc_pl"], forms2["loc_pl"], forms2["ins_pl"])
    		
    		if matches_any(word, {"vták", "vlk", "pes"}) then
    			switch_plural_forms(forms) --changes the prefered forms
    		
	    		-- Determine alternative dative and locative for some nouns
			    if matches_any(word, {"vlk", "pes"}) then
			        set_forms(forms, {"dat_sg2", "loc_sg2"}, {stem .. "u", stem .. "u"})
			    end
			end
	    end
    end

    return forms, title  -- Return forms table and title for declension
end

declensions["stroj"] = function(word, genitive)
	local forms = {}
    local mobile_removed = false  -- Tracks if a mobile vowel was removed
    local words_with_no_mobile = {"kúpeľ", "červeň", "kameň", "jačmeň", "koreň",
    	"prameň", "prsteň", "jeleň"}
    
    -- Determine the stem by removing mobile vowels if applicable
    local stem = word
    if ends_with_any(word, {"ec", "iec", "ac", "[^ti]eľ", "oľ", "eň", "eť", "[^i]er", "el"})
    	and not matches_any(word, words_with_no_mobile)
    then
    	stem = remove_mobile_vowel(word)
        mobile_removed = true  -- Mark mobile vowel as removed
    end

    -- Adjust specific stems based on predefined mappings
    stem = ({["dážď"] = "dažď", ["kôš"] = "koš", ["nôž"] = "nož",
        ["kôň"] = "koň", ["timbre"] = "timbr"})[word] or stem
    
    -- Pluralia tantum
    if NUMBER == "pl" then
    	stem = remove_suffix(word, "e")
    end
    
    -- Override if a specific argument is given
    if genitive and NUMBER ~= "pl" then 
        stem = remove_last_char(genitive)
        
        if stem == word then
        	mobile_removed = false
        end
    end

    -- Set "mobile_removed" if genitive was set by the user
    if ends_with(word, "e[lr]") and ends_with(stem, "[^e][lr]") then
        mobile_removed = true
    end

    -- Title for the declension table
    local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("stroj") .. ")"
    if GENDER == "m-anml" then
    	if PLURAL2 then
    		title = "Declension of ''" .. word .. "'' (patterns " .. pattern_link("chlap") .. " <small>(singular, plural 2)</small> and " .. pattern_link("stroj") .. " <small>(plural 1)</small>)"
    	else
    		title = "Declension of ''" .. word .. "'' (patterns " .. pattern_link("chlap") .. " <small>(singular)</small> and " .. pattern_link("stroj") .. " <small>(plural)</small>)"
    	end
    elseif word == "cól" then
    	title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("stroj") .. " or " .. pattern_link("dub") .. ")"
    end

    -- Helper variables for analyzing the last and penultimate characters
    local ultimate, penultimate = get_last_letters(stem)
    local first = get_first_char(word)

    -- Determine genitive singular ending
    local gen_sg = "a"
    if (matches_any(TYPE, {"abstr", "material"})
        or matches_any(GEN_ENDING, {"u", "u/a"}))
        and not matches_any(word, {"jačmeň", "kameň", "olej", "loj", "vývoj", "rozvoj"})
    then
        gen_sg = "u"
    end
    
    -- Override genitive singular ending if a specific argument is given
    if genitive then 
        gen_sg = get_last_char(genitive)
    end

    -- Determine genitive plural ending
    local gen_pl = stem .. "ov"  -- Default genitive plural ending is "ov"
    
    -- Adjust genitive plural for specific place names and patterns
    if is_capital(first) and (ends_with_any(word, {"áre", "iare"})
        or word == "Tlmače")
    then
        local gen_stem = remove_last_char(word)  -- Remove last character to get the base stem
        local syllable_count, syllables = split_into_syllables(gen_stem)
        local last_syllable = syllables[1]
        local penultimate_syllable = syllables[2] or ""
        
        -- Lengthen the vowel if the penultimate syllable is short, otherwise use unchanged stem
        if is_long(penultimate_syllable) then
            gen_pl = gen_stem
        else
            gen_pl = remove_suffix(gen_stem, last_syllable) .. lengthen_vowel(last_syllable)
        end
    end

    -- Special cases for genitive plural of specific words
    if word == "Ladce" then
        gen_pl = "Ladiec"
    elseif word == "Vráble" then
        gen_pl = "Vrábeľ"
    elseif word == "peniaze" then
    	gen_pl = "peňazí"
    elseif matches_any(word, {"deň", "kôň", "groš"}) then
    	gen_pl = append_ending(stem, "í")
    end

    -- Determine instrumental plural ending
    local ins_pl = "mi"  -- Default instrumental plural ending is "mi"
    if mobile_removed
        or (obstruents_set[penultimate] and obstruents_set[ultimate])
    then
        ins_pl = "ami"
    end

    -- Populate forms table with singular and plural endings
    append_endings(forms, word, stem,
        gen_sg, "u", nil, "i", "om",
        "e", gen_pl, "om", nil, "och", ins_pl
    )
    
    -- Exceptions
    if matches_any(word, {"deň", "poldeň"}) then
    	set_forms(forms,
    		{"nom_pl", "acc_pl", "loc_sg2"},
    		{append_ending(stem, "i"), append_ending(stem, "i"), append_ending(stem, "e")}
    	)
    elseif word == "ďateľ" then
    	set_forms(forms,
    		{"gen_sg2", "dat_sg2", "loc_sg2", "ins_sg2",
    			"nom_pl2", "gen_pl2", "dat_pl2", "acc_pl2", "loc_pl2", "ins_pl2"},
    		{"ďatľa", "ďatľovi", "ďatľovi", "ďatľom",
    			"ďatle", "ďatľov", "ďatľom", "ďatle", "ďatľoch", "ďatľami"}
    	)
    elseif word == "cól" then
    	set_forms(forms,
    		{"loc_sg2", "nom_pl2", "acc_pl2"},
    		{"cóle", "cóly", "cóly"}
    	)
    end
    
    -- Animal nouns
    if GENDER == "m-anml" then
    	append_animal_singular(forms, stem)
    	
    	if PLURAL2 then
    		local forms2, title2 = declensions["chlap"](word, stem .. gen_sg)
    		append_second_plural(forms, forms2["nom_pl"], forms2["gen_pl"], forms2["dat_pl"],
    			forms2["acc_pl"], forms2["loc_pl"], forms2["ins_pl"])
	    end
    end

    return forms, title  -- Return forms table and title for declension
end

declensions["žena"] = function(word, genitive)
    local forms = {}
	local stem = remove_suffix(word, "a")
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("žena") .. ")"
	if ends_with(word, "ea") then
		title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("žena") .. ", loanword ending with ''-ea'')"
	end
	
	-- Pluralia tantum or genitive
    if NUMBER == "pl" then
    	stem = remove_suffix(word, "y")
    end
    if genitive and NUMBER ~= "pl" then
    	stem = remove_suffix(genitive, "y")
    end
	
	-- Split into syllables
	local gen_pl = ""
	local syllable_count, syllables = split_into_syllables(stem)
    local word_syllable_count, word_syllables = split_into_syllables(word)

    -- Get the last and second-to-last syllables for rule checks
    local last_syllable = syllables[1]
    local penultimate_syllable = syllables[2] or ""
    local word_penultimate_syllable = word_syllables[2] or ""
	
	local ultimate, penultimate = get_last_letters(stem)
	
	-- 1. Add a vowel between the last two consonants of a cluster at the end of the stem
    if ends_with_consonant_cluster(last_syllable) or word_syllable_count == 1 then
        if (ultimate == "k" and not matches_any(word, {"banka"}))
        	or (ultimate == "b" and (TYPE == "der" or not sonorants_set[penultimate]))
        	or sonorants_set[ultimate]
        	or matches_any(word, {"farba", "karta", "buchta", "astma"})
        	or (obstruents_set[penultimate] and ultimate == "v")
        then
            if penultimate ~= "j" and (word_syllable_count == 1 or is_short(word_penultimate_syllable)) then
                gen_pl = harden_last_consonant(remove_suffix(stem, ultimate)) .. "ie" .. ultimate
                
                if matches_any(word, {"handra", "perla", "metla", "slivka",
                	"tehla", "vidly", "karta", "kvapka", "stovka", "doska"})
                then
                	gen_pl = remove_suffix(stem, ultimate) .. "á" .. ultimate
                	set_forms(forms, {"gen_pl2"}, {harden_last_consonant(remove_suffix(stem, ultimate)) .. "ie" .. ultimate})
                end
                
                if matches_any(word, {"jamka", "kvapka"}) then
                	set_forms(forms, {"gen_pl2"}, {remove_suffix(stem, ultimate) .. "ô" .. ultimate})
                end
                
                if matches_any(word, {"astma"}) then
                	set_forms(forms, {"gen_pl2"}, {stem .. "í"})
                end
            elseif obstruents_set[ultimate] and (is_long(word_penultimate_syllable) or penultimate == "j") then
                gen_pl = remove_suffix(stem, ultimate) .. "o" .. ultimate
            elseif (sonorants_set[ultimate] or ultimate == "v") and (is_long(word_penultimate_syllable) or penultimate == "j") then
                gen_pl = remove_suffix(stem, ultimate) .. "e" .. ultimate
                
                if penultimate ~= "j" then
                	set_forms(forms, {"gen_pl2"}, {remove_suffix(stem, ultimate) .. "ie" .. ultimate})
                end
            end
        end
    end

    -- 2. Add "í" if stem ends with a vowel or a soft consonant
    if gen_pl == "" and (vowels_set[ultimate]
    	or matches_any(word, {"medaila", "pera", "kanva", "panva", "skepsa", "nuansa"}))
    then
        gen_pl = stem .. "í"
        
        if matches_any(word, {"nuansa"}) then
        	set_forms(forms, {"gen_pl2"}, {remove_suffix(stem, last_syllable) .. lengthen_vowel(last_syllable)})
        end
    end
	
	-- 3. Return the stem with no change if conditions match
    if gen_pl == "" and (is_long(penultimate_syllable)
    	or ends_with_any(stem, {"tvor", "ov"})
    	or mw.ustring.find(last_syllable, "jo")
    	or (TYPE == "loan" and matches_any(get_vowel(last_syllable), {"e", "o"}))) then
        gen_pl = stem
    end

	-- 4. Logic to use the lengthened vowel if conditions match
	if gen_pl == "" and (not (ends_with_consonant_cluster(last_syllable) or vowels_set[ultimate])
	    or (ends_with_consonant_cluster(last_syllable) and sonorants_set[penultimate] and obstruents_set[ultimate]))
	    or matches_any(word, {"pocta", "cesta", "brzda", "hrazda", "jazda", "uzda", "vražda"}) then
	    gen_pl = remove_suffix(stem, last_syllable) .. lengthen_vowel(last_syllable)
	end
	
	-- 5. long -á- instead of -ia- for some loanwords
	if matches_any(word, {"pyžama", "šachta"}) then
		gen_pl = (word == "pyžama") and "pyžám" or "šácht"
		
		if word == "šachta" then
        	set_forms(forms, {"gen_pl2"}, {"šachiet"})
        end
	end
	
	-- 6. Exceptions
	if word == "mzda" then
		gen_pl = "miezd"
	end
    
    if gen_pl == "" then gen_pl = stem end
    
    local dat_pl, loc_pl
	if is_long(word_penultimate_syllable) then
		dat_pl, loc_pl = "am", "ach"
	else
		dat_pl, loc_pl = "ám", "ách"
	end
	
	local dat_sg, loc_sg
	if ultimate == "e" then
		dat_sg, loc_sg = "i", "i"
	else
		dat_sg, loc_sg = "e", "e"
	end
	
    append_endings(forms, word, stem,
    	"y", dat_sg, "u", loc_sg, "ou",
    	"y", gen_pl, dat_pl, nil, loc_pl, "ami"
    )
    
    if matches_any(word, {"zora", "žiara", "žiabra"}) then
    	set_forms(forms,
    		{"nom_pl", "acc_pl"},
    		{stem .. "e", stem .. "e"}
    	)
    end
 
    return forms, title
end

declensions["gazdiná"] = function(word, genitive)
    local forms = {}
	local stem = remove_suffix(word, "á")
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("gazdiná") .. ")"
	
	local syllable_count, syllables = split_into_syllables(stem)
	local last_syllable = syllables[1]
	
	local gen_pl = ""
	if ends_with_consonant_cluster(last_syllable) then
		gen_pl = mw.ustring.sub(stem, 1, -2) .. "ien"
	else
		gen_pl = remove_suffix(stem, last_syllable) .. lengthen_vowel(last_syllable)
	end
	
    append_endings(forms, word, stem,
    	"ej", "ej", "ú", "ej", "ou",
    	"é", gen_pl, "ám", nil, "ách", "ami"
    )
 
    return forms, title
end

declensions["ulica"] = function(word, genitive)
    local forms = {}
	local stem = remove_suffix(word, "a")
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("ulica") .. ")"
	
	-- Pluralia tantum
    if NUMBER == "pl" then
    	stem = remove_suffix(word, "e")
    end
	
	-- Split into syllables
	local gen_pl = ""
	local syllable_count, syllables = split_into_syllables(stem)
    local word_syllable_count, word_syllables = split_into_syllables(word)

    -- Get the last and second-to-last syllables for rule checks
    local last_syllable = syllables[1]
    local penultimate_syllable = syllables[2] or ""
    local word_penultimate_syllable = word_syllables[2] or ""
	
	local ultimate, penultimate = get_last_letters(stem)
	local first = get_first_char(word)
	
	-- 1. Add a vowel between the last two consonants of a cluster at the end of the stem
    if ends_with_consonant_cluster(last_syllable) then
        if (is_capital(first) and ends_with(word, "ce"))
        	or matches_any(word, {"dverce", "ovca", "fakľa", "drumbľa", "husle", "jasle",
        		"kachle", "hrable", "šabľa", "mašľa", "žemľa", "ríbezľa", "čerešňa", "sukňa", "višňa"})
        then
            gen_pl = harden_last_consonant(remove_suffix(stem, ultimate)) .. "ie" .. ultimate
            
            if matches_any(word, {"drumbľa", "husle", "jasle", "kachle", "hrable",
            	"šabľa", "mašľa", "žemľa", "ríbezľa", "čerešňa", "sukňa", "višňa"}) then
	    		set_forms(forms, {"gen_pl2"}, {append_ending(stem, "í")})
	    	end
        end
    end

    -- 2. Add "í" if stem ends with a vowel or a soft consonant
    if gen_pl == "" and (matches_any(ultimate, {"dz", "dž", "ž", "ť", "ď", "j", "i", "y"})
    	or ends_with_any(word, {"nca", "oľa", "ôľa", "aľa", "ša", "ča"})
    	or matches_any(word, {"liace", "pasca", "páľa", "trúbeľa", "mrľa", "konope", "večera", "rozopra"})
    	or (matches_any(ultimate, {"ľ"}) and consonants_set[penultimate])
    	or (matches_any(ultimate, {"ň"}) and penultimate ~= "y"))
    	and not matches_any(word, {"papuča", "priča", "hrča", "paprča",
    		"garniža", "fakľa", "skriňa", "sviňa", "abatiša", "čaša", "fľaša",
    		"Krkonoše", "ríša", "skrýša"})
    then
        gen_pl = append_ending(stem, "í")
    end

	-- 3. Logic to use the lengthened vowel if conditions match
	if gen_pl == "" and not ends_with_consonant_cluster(last_syllable) and not vowels_set[ultimate]
	    and ((ends_with(word, "ca") and TYPE ~= "der")
	    	or ends_with_any(word, {"yňa", "uľa"})
	    	or matches_any(word, {"papuča", "priča", "hrča", "chvíľa", "míľa", "košeľa", "nedeľa",
	    		"guľa", "hoľa", "homoľa", "skriňa", "sviňa", "abatiša", "čaša", "fľaša",
	    		"Krkonoše", "ríša", "skrýša", "garniža", "Hybe", "dvere", "kuša", "moruša"}))
	then
		if is_long(penultimate_syllable) then
			gen_pl = stem
		else
	    	gen_pl = remove_suffix(stem, last_syllable) .. lengthen_vowel(last_syllable)
	    	if matches_any(word, {"homoľa", "kuša", "moruša", "hrča", "paprča",
	    		"guľa", "hoľa"})
	    	then
	    		set_forms(forms, {"gen_pl2"}, {append_ending(stem, "í")})
	    	end
	    end
	end
    
    if gen_pl == "" then gen_pl = stem end
    
    local dat_pl, loc_pl
	if ends_with_any(stem, {"i", "y"}) then
		dat_pl, loc_pl = "ám", "ách"
	elseif is_long(word_penultimate_syllable) or ends_with(stem, "j") then
		dat_pl, loc_pl = "am", "ach"
	else
		dat_pl, loc_pl = "iam", "iach"
	end
	
    append_endings(forms, word, stem,
    	"e", "i", "u", "i", "ou",
    	"e", gen_pl, dat_pl, nil, loc_pl, "ami"
    )
    
    if word == "rozopra" then
    	set_forms(forms,
    		{"dat_pl2", "loc_pl2"},
    		{"rozoprám", "rozoprách"}
    	)
    elseif word == "dvere" then
    	set_forms(forms,
    		{"dat_pl", "loc_pl", "gen_pl2", "ins_pl2"},
    		{"dverám", "dverách", "dverí", "dvermi"}
    	)
    end
 
    return forms, title
end

declensions["irregular"] = function(word, genitive)
    local forms = {}
	local title = "Declension of ''" .. word .. "'' (irregular)"
    
    if matches_any(word, {"mať", "mater", "mati"}) then
    	local stem = "mater"
    	append_endings(forms, word, stem,
	    	"e", "i", "", "i", "ou",
	    	"e", stem .. "í", "iam", nil, "iach", "ami"
	    )
	    local accusative = (word == "mater") and {"mater", "mať"} or {"mať", "mater"}
	    set_forms(forms, {"acc_sg", "acc_sg2"}, accusative)
	elseif ends_with(word, "pani") then
		local stem = remove_suffix(word, "ni") .. "ň"
		append_endings(forms, word, stem,
	    	"ej", "ej", "iu", "ej", "ou",
	    	"ie", stem .. "í", "iam", nil, "iach", "iami"
	    )
    end
 
    return forms, title
end

declensions["dlaň"] = function(word, genitive)
    local forms = {}
    local words_with_mobile_vowel = {"faloš", "osuheľ", "siheľ", "myseľ"}
	local stem = (matches_any(word, words_with_mobile_vowel) or ends_with(word, "eň"))
		and remove_mobile_vowel(word) or word
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("dlaň") .. ")"
	
	local syllable_count, syllables = split_into_syllables(word)
	local last_syllable = syllables[1]
	local penultimate_syllable = syllables[2] or ""
	local last_char = get_last_char(word)
	
	local dat_pl, loc_pl
	
	if is_long(penultimate_syllable) or last_char == "j" then
		dat_pl, loc_pl = "am", "ach"
	elseif word == "kader" then
		dat_pl, loc_pl = "ám", "ách"
	else
		dat_pl, loc_pl = "iam", "iach"
	end
	
    append_endings(forms, word, stem,
    	"e", "i", nil, "i", "ou",
    	"e", append_ending(stem, "í"), dat_pl, nil, loc_pl, "ami"
    )
    
    if matches_any(word, {"myseľ", "tvár", "hneď", "raž"}) then
    	set_forms(forms, {"gen_sg2"}, {append_ending(stem, "i")})
    end
 
    return forms, title
end

declensions["kosť"] = function(word, genitive)
    local forms = {}
    local words_with_mobile_vowel = {"voš", "lož", "Ves", "cirkev", "reďkev"}
	local stem = (matches_any(word, words_with_mobile_vowel))
		and remove_mobile_vowel(word) or word
	if word == "česť" then stem = "cť" end
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("kosť") .. ")"
	
    append_endings(forms, word, stem,
    	"i", "i", nil, "i", "ou",
    	"i", append_ending(stem, "í"), "iam", nil, "iach", "ami"
    )
    
    if word == "hrsť" then
    	set_forms(forms,
    		{"nom_pl", "acc_pl"},
    		{"hrste", "hrste"}
    	)
    elseif word == "lesť" then
    	append_alternative_singular(forms, "ľsti", "ľsti", nil, "ľsti", "ľsťou")
    	append_alternative_plural(forms, "ľsti", "ľstí", "ľstiam", nil, "ľstiach", "ľsťami")
    elseif matches_any(word, {"cirkev", "reďkev"}) then
    	set_forms(forms,
    		{"dat_pl", "loc_pl"},
    		{stem .. "ám", stem .. "ách"}
    	)
    end
 
    return forms, title
end
 
declensions["mesto"] = function(word, genitive)
    local forms = {}
    local ultimate, penultimate = get_last_letters(word)
	
	local special_type = ""
	local stem = remove_suffix(word, "o")
	
	if penultimate == "u" and ultimate == "m" then
		stem = remove_suffix(word, "um")
		special_type = ", of Latin origin ending with ''-um''"
	elseif penultimate == "o" and ultimate == "n" then
		stem = remove_suffix(word, "on")
		special_type = ", of Greek origin ending with ''-on''"
	elseif penultimate == "m" and ultimate == "ä" then
		stem = remove_suffix(word, "ä") .. "en"
		special_type = ", archaic type ending with ''-mä''"
	end
	
	-- Pluralia tantum
    if NUMBER == "pl" then
    	stem = remove_suffix(word, ultimate)
    end
	
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("mesto") .. special_type .. ")"
	
	local stem_ultimate, stem_penultimate = get_last_letters(stem)
	
	local loc_sg = "e"
	if vowels[stem_ultimate] or matches_any(stem_ultimate, {"k", "g", "ch", "h"}) then
		loc_sg = "u"
	elseif matches_any(word, {"vnútro", "nebo"}) then
		loc_sg = "i"
	end
	
	-- Split into syllables
	local gen_pl = ""
	local syllable_count, syllables = split_into_syllables(stem)
    local word_syllable_count, word_syllables = split_into_syllables(word)

    -- Get the last and second-to-last syllables for rule checks
    local last_syllable = syllables[1]
    local penultimate_syllable = syllables[2] or ""
    local word_penultimate_syllable = word_syllables[2] or ""
	
	-- 1. Add a vowel between the last two consonants of a cluster at the end of the stem
    if ends_with_consonant_cluster(last_syllable) then
        if matches_any(word, {"jedlo", "predjedlo", "jutro", "vrecko", "brvno"}) then
            if matches_any(word, {"jedlo", "predjedlo", "jutro"}) then
            	gen_pl = remove_suffix(stem, stem_ultimate) .. "á" .. stem_ultimate
            else
            	gen_pl = remove_suffix(stem, stem_ultimate) .. "ie" .. stem_ultimate
            	set_forms(forms, {"gen_pl2"}, {remove_suffix(stem, stem_ultimate) .. "á" .. stem_ultimate})
            end
        elseif sonorants_set[stem_ultimate] or ends_with_any(last_syllable, {"stv", "ctv", "íčk", "ečk", "očk"}) or TYPE == "dim" then
            if stem_penultimate ~= "j" and (word_syllable_count == 1 or ends_with_any(last_syllable, {"stv", "ctv"}) or is_short(word_penultimate_syllable)) then
                gen_pl = remove_suffix(stem, stem_ultimate) .. "ie" .. stem_ultimate
            elseif obstruents_set[stem_ultimate] and (is_long(word_penultimate_syllable) or stem_penultimate == "j") then
                gen_pl = remove_suffix(stem, stem_ultimate) .. "o" .. stem_ultimate
            elseif sonorants_set[stem_ultimate] and (is_long(word_penultimate_syllable) or stem_penultimate == "j") then
                gen_pl = remove_suffix(stem, stem_ultimate) .. "e" .. stem_ultimate
                if stem_penultimate ~= "j" then
                	set_forms(forms, {"gen_pl2"}, {remove_suffix(stem, stem_ultimate) .. "ie" .. stem_ultimate})
                end
            end
        end
    end

    -- 2. Add "í" if stem ends with a vowel or a soft consonant
    if gen_pl == "" and (vowels_set[stem_ultimate] or soft_consonants_set[stem_ultimate]) then
        gen_pl = stem .. "í"
    end
	
	-- 3. Return the stem with no change if conditions match
    if gen_pl == "" and (is_long(penultimate_syllable)
    	or ends_with_any(stem, {"vojsk", "ov"})
    	or (TYPE == "loan" and matches_any(get_vowel(last_syllable), {"e", "o"}))) then
        gen_pl = stem
    end

	-- 4. Logic to use the lengthened vowel if conditions match
	if gen_pl == "" and (not (ends_with_consonant_cluster(last_syllable) or vowels_set[stem_ultimate])
	    or (ends_with_consonant_cluster(last_syllable) and obstruents_set[stem_ultimate])) then
	    gen_pl = remove_suffix(stem, last_syllable) .. lengthen_vowel(last_syllable)
	end

    local nom_pl = "á"
    if NUMBER == "pl" then
    	nom_pl = ultimate
    end
    
    if gen_pl == "" then gen_pl = stem end
    
    if is_long(word_penultimate_syllable) or word == "jojo" then
    	append_endings(forms, word, stem,
	    	"a", "u", nil, loc_sg, "om",
	    	"a", gen_pl, "am", nil, "ach", "ami"
	    )
    else
	    append_endings(forms, word, stem,
	    	"a", "u", nil, loc_sg, "om",
	    	nom_pl, gen_pl, "ám", nil, "ách", "ami"
	    )
	end
    
    if matches_any(word, {"oko", "ucho"}) then
    	local pl_stem = (word == "oko") and "oč" or "uš"
    	append_second_plural(forms, pl_stem .. "i", pl_stem .. "í", pl_stem .. "iam",
    		nil, pl_stem .. "iach", pl_stem .. "ami")
    	switch_plural_forms(forms) --changes the prefered forms
    	set_forms(forms,
    		{"gen_pl2"},
    		{pl_stem .. "ú"}
    	)
    elseif word == "nebo" then
    	set_forms(forms,
    		{"nom_pl", "nom_pl2", "gen_pl", "dat_pl", "dat_pl2", "acc_pl",
    			"acc_pl2", "loc_pl", "loc_pl2", "ins_pl"},
    		{"nebesá", "nebesia", "nebies", "nebesám", "nebesiam", "nebesá",
    			"nebesia", "nebesách", "nebesiach", "nebesami"}
    	)
    end
    
    return forms, title
end
 
declensions["srdce"] = function(word, genitive)
	local forms = {}
	local stem = remove_suffix(word, "e")
	if ends_with(word, "ě") then
		stem = remove_suffix(word, "ě")
	end
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("srdce") .. ")"
	
	if NUMBER == "pl" then
		if ends_with(word, "ia") then
    		stem = remove_suffix(word, "ia")
    	else
    		stem = remove_suffix(word, "a")
    	end
    end
	
	-- Split into syllables
	local gen_pl = ""
	local syllable_count, syllables = split_into_syllables(stem)
	local word_syllable_count, word_syllables = split_into_syllables(word)
	
	-- Get the last and second-to-last syllables for rule checks
	local last_syllable = syllables[1]
	local penultimate_syllable = syllables[2] or ""
	local word_penultimate_syllable = word_syllables[2] or ""
	
	local last_char = get_last_char(stem)
	local stem_ultimate, stem_penultimate = get_last_letters(stem)
	
	-- 1 & 2. Lengthen the last syllable's vowel if conditions apply,
	-- otherwise leave the stem unchanged if penultimate syllable is long
	if not ends_with_consonant_cluster(last_syllable) 
		or (ends_with(word, "ce") and matches_any(get_vowel(word_penultimate_syllable), {"r", "l"}) and TYPE ~= "dim")
		or ends_with(word, "ište") then
	    if is_long(penultimate_syllable) then
	        gen_pl = stem  -- If penultimate syllable of the stem is long, use the stem unchanged
	    else
	        -- Otherwise, lengthen the last syllable's vowel
	        gen_pl = remove_suffix(stem, last_syllable) .. lengthen_vowel(last_syllable)
	    end
	end
	
	-- 3. Add a vowel between the last two consonants if the conditions apply
	if gen_pl == "" and (word == "citoslovce" or word == "vajce"
		or ends_with(word, "ce")) then
	    if stem_penultimate ~= "j" and is_short(word_penultimate_syllable) then
	        gen_pl = remove_suffix(stem, stem_ultimate) .. "ie" .. stem_ultimate
	    elseif is_long(word_penultimate_syllable) or stem_penultimate == "j" then
	        gen_pl = remove_suffix(stem, stem_ultimate) .. "e" .. stem_ultimate
	    end
	end
	
	-- 4. Add "í" to the stem for specific words
	if matches_any(word, {"more", "oje", "pole", "lože"}) then
	    gen_pl = stem .. "í"
	end
	
	-- Fallback: if none of the rules apply, return the original stem
	if gen_pl == "" then gen_pl = stem end
	
	if ends_with_any(gen_pl, {"d", "t", "n", "l"}) then
		gen_pl = soften_last_consonant(gen_pl)
	end
	
	if is_long(word_penultimate_syllable) or ends_with(stem, "j") then
	    append_endings(forms, word, stem,
	    	"a", "u", nil, "i", "om",
	    	"a", gen_pl, "am", nil, "ach", "ami"
	    )
	else
		append_endings(forms, word, stem,
	    	"a", "u", nil, "i", "om",
	    	"ia", gen_pl, "iam", nil, "iach", "ami"
	    )
	end
    
    return forms, title
end

declensions["vysvedčenie"] = function(word, genitive)
    local forms = {}
	local stem = remove_suffix(word, "ie")
	if ends_with(word, "í") then
		stem = remove_suffix(word, "í")
	end
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("vysvedčenie") .. ")"
	
    append_endings(forms, word, stem,
    	"ia", "iu", nil, "í", "ím",
    	"ia", stem .. "í", "iam", nil, "iach", "iami"
    )
    
    if word == "storočie" then
    	append_second_plural(forms, "stáročia", "stáročí", "stáročiam",
    		nil, "stáročiach", "stáročiami")
    end
 
    return forms, title
end

declensions["dievča"] = function(word, genitive)
    local forms = {}
	local stem = remove_suffix(word, "a")
	if ends_with(word, "ä") then
		stem = remove_suffix(word, "ä")
	end
	local ending = get_last_char(word)
	local title = "Declension of ''" .. word .. "'' (pattern " .. pattern_link("dievča") .. ")"
	
	local gen_pl = ""
	local syllable_count, syllables = split_into_syllables(word)
	local last_syllable = syllables[1]
	local penultimate_syllable = syllables[2] or ""
	
	if is_long(penultimate_syllable) then
        gen_pl = stem .. ending .. "t"
    else
        gen_pl = append_ending(stem, "iat")
    end
	
    append_endings(forms, word, stem,
    	ending .. "ťa", ending .. "ťu", nil, ending .. "ti", ending .. "ťom",
    	ending .. "tá", gen_pl, ending .. "tám", nil, ending .. "tách", ending .. "tami"
    )
    
    local pl_stem = harden_last_consonant(stem)
    append_second_plural(forms, pl_stem .. "ence", pl_stem .. "eniec", pl_stem .. "encom",
    	nil, pl_stem .. "encoch", pl_stem .. "encami")
    
    if matches_any(word, {"kura", "strídža", "drumblence", "gajdence", "deťúrence"}) then
    	switch_plural_forms(forms) --changes the prefered forms
    	unset_alt_forms(forms)
    elseif matches_any(word, {"páža", "knieža", "kurča", "plánča", "pôrča",
    	"zviera", "pachoľa", "mláďa", "dúpä", "chlápä", "žieňa", "nemluvňa"}) then
    	unset_alt_forms(forms)
    elseif matches_any(word, {"prasa", "teľa", "šteňa"}) then
    	local pl_stem = (word == "šteňa") and "šten" or stem
    	append_second_plural(forms, pl_stem .. "ce", append_ending(pl_stem, "iec"),
    		pl_stem .. "com", nil, pl_stem .. "coch", pl_stem .. "cami")
    	switch_plural_forms(forms) --changes the prefered forms
    elseif word == "dieťa" then
    	set_forms(forms,
    		{"nom_pl", "gen_pl", "dat_pl", "acc_pl", "loc_pl", "ins_pl"},
    		{"deti", "detí", "deťom", "deti", "deťoch", "deťmi"}
    	)
    	unset_alt_forms(forms)
    end
 
    return forms, title
end

declensions["adjective"] = function(word, genitive)
    local forms = {}
	local title = "Declension of ''" .. word .. "'' (adjective declension)"
	local ultimate, penultimate = get_last_letters(word)
	
	local lemma_form = word
	if NUMBER == "pl" then
		if ends_with(word, "é") then
			lemma_form = remove_suffix(word, "é") .. "ý"
		elseif ends_with(word, "ie") then
			lemma_form = remove_suffix(word, "ie") .. "í"
		elseif ends_with_any(word, {"ove", "ine"}) then
			lemma_form = remove_suffix(word, "e")
		elseif ends_with(word, "e") then
			lemma_form = remove_suffix(word, "e") .. "y"
		elseif ends_with(word, "í") and not soft_consonants_set[penultimate] then
			lemma_form = remove_suffix(word, "í") .. "ý"
		elseif ends_with(word, "i") and not soft_consonants_set[penultimate] then
			lemma_form = remove_suffix(word, "i") .. "y"
		end
	else
		if GENDER == "f" or GENDER == "n" then
			if ends_with_any(word, {"á", "é"}) then
				lemma_form = remove_last_char(word) .. "ý"
			elseif ends_with_any(word, {"ia", "ie"}) then
				lemma_form = remove_suffix(remove_last_char(word), "i") .. "í"
			elseif ends_with_any(word, {"ova", "ovo", "ina", "ino"}) then
				lemma_form = remove_last_char(word)
			elseif ends_with_any(word, {"a", "e"}) then
				lemma_form = remove_last_char(word) .. "y"
			end
		end
	end
	
	local forms_raw = m_adj.do_generate_forms({pagename=lemma_form}, "adjective").forms
	
	set_forms(forms,
		{"nom_sg"},
		{forms_raw["nom_" .. get_first_char(GENDER)][1].form}
	)
	
	-- Singular
    if GENDER == "f" then
    	set_forms(forms,
    		{"gen_sg", "dat_sg", "loc_sg", "ins_sg"},
    		{
    			forms_raw["gen_f"][1].form, forms_raw["dat_f"][1].form,
    			forms_raw["loc_f"][1].form, forms_raw["ins_f"][1].form
    		}
    	)
    else
    	set_forms(forms,
    		{"gen_sg", "dat_sg", "loc_sg", "ins_sg"},
    		{
    			forms_raw["gen_mn"][1].form, forms_raw["dat_mn"][1].form,
    			forms_raw["loc_mn"][1].form, forms_raw["ins_mn"][1].form
    		}
    	)
    end
    
    -- special accusative singular
	if GENDER == "m-pr" or GENDER == "m-anml" then
		set_forms(forms,
    		{"acc_sg"},
    		{forms_raw["acc_m_an"][1].form}
    	)
	elseif GENDER == "m-in" then
		set_forms(forms,
    		{"acc_sg"},
    		{forms_raw["acc_m_in"][1].form}
    	)
    else
    	set_forms(forms,
			{"acc_sg"},
			{forms_raw["acc_" .. get_first_char(GENDER)][1].form}
		)
    end
	
	-- Plural
	set_forms(forms,
		{"gen_pl", "dat_pl", "loc_pl", "ins_pl"},
		{
			forms_raw["gen_p"][1].form, forms_raw["dat_p"][1].form,
			forms_raw["loc_p"][1].form, forms_raw["ins_p"][1].form
		}
	)
	
	if GENDER == "m-pr" then
		set_forms(forms,
    		{"nom_pl", "acc_sg", "acc_pl"},
    		{forms_raw["nom_mp_an"][1].form, forms_raw["acc_m_an"][1].form, forms_raw["acc_mp_an"][1].form}
    	)
    else
    	set_forms(forms,
    		{"nom_pl", "acc_pl"},
    		{forms_raw["nom_fnp"][1].form, forms_raw["acc_fnp"][1].form}
    	)
	end
 
    return forms, title
end

declensions["indeclinable"] = function(word, genitive)
	local forms = {}
	local title = "Declension of ''" .. word .. "'' (indeclinable)"
	
	local cases = {"nom", "gen", "dat", "acc", "loc", "ins"}  -- List of cases
    local numbers = {"sg", "pl"}
    for _, case in ipairs(cases) do
        for _, number in ipairs(numbers) do
            local form_key = case .. "_" .. number
            forms[form_key] = word
        end
    end
    
    return forms, title
end

--[=[
    Partial declination functions
]=]--

function check_gender(gender)
	if gender == nil then
		return error("No gender entered. Please pass one of these values as parameter 1: m-pr, m-anml, m-in, f, n.")
	elseif allowed_genders_set[gender] then
		return gender
	else
		return error("Unknown gender. Please pass one of these values as parameter 1: m-pr, m-anml, m-in, f, n.")
	end
end

function determine_pattern(word, genitive)
	local pattern
	
	local ultimate, penultimate = get_last_letters(word)
	local first = get_first_char(word)
	
	local dlan_endings = m_table.listToSet({"ň", "č", "ž", "ľ", "ď", "j", "š", "m", "z", "dz", "x"})
	local dlan_exceptions_neg = m_table.listToSet({"reč", "seč", "lož", "beľ", "soľ", "mlaď", "meď", "myš", "voš"})
	local dlan_exceptions_pos = m_table.listToSet({"obec", "pec", "čelusť", "kysť", "päsť", "Provence"})
	local dlan_end_with_r_t = m_table.listToSet({"kader", "neter", "šír", "tvár",
		"činovať", "drobäť", "droboť", "hať", "hrochoť", "Hrochoť", "hrsť", "inovať", "labuť",
		"niť", "obeť", "paruť", "pažiť", "pečať", "perepúť", "perleť", "peruť", "pípeť", "plť",
		"postať", "prť", "púť", "sieť", "sihoť", "stať", "štvrť", "trať", "úvrať", "vňať",
		"violeť", "záhať", "žlť"})
	local stroj_exceptions_pos = {"timbre", "cól", "gáfor", "hámor", "kôpor",
		"kufor", "Pôtor", "šiator", "pedál", "sandál", "kanál", "peniaz"}
	local stroj_exceptions_neg = {"nesvár", "nešvár", "pár", "suchopár", "svár"}
	
	if matches_any(ultimate, {"r", "l"}) and penultimate == "e" and genitive == nil then
		error("For nouns ending with er or el, the genitive form must be specified as parameter 2")
	end
	
	if mw.ustring.find(word, " ") then
		return "indeclinable"
	end
	
	if NUMBER == "pl" then
		if matches_any(ultimate, {"é", "i", "í"}) or (ultimate == "e" and ends_with(genitive, "ch")) then
			pattern = "adjective"
		elseif GENDER == "m-in" then
			if ultimate == "y" then
				pattern = "dub"
			else
				pattern = "stroj"
			end
		elseif GENDER == "f" then
			if ultimate == "y" then
				pattern = "žena"
			else
				pattern = "ulica"
			end
		elseif GENDER == "n" then
			if ultimate == "á" then
				pattern = "mesto"
			elseif penultimate == "i" and ultimate == "a" then
				pattern = "srdce"
			else
				if soft_consonants_set[penultimate] then
					pattern = "srdce"
				else
					pattern = "mesto"
				end
			end
		end
	else
		if GENDER == "m-pr" then
			if (matches_any(ultimate, {"y", "ý", "i", "í"}) and not ends_with_any(genitive, {"[yií]ho", "a"}))
				or (matches_any(penultimate .. ultimate, {"ov", "in"}) and ends_with_any(genitive, {"ovho", "inho"}))
			then
				pattern = "adjective"
			elseif ultimate == "a" then
				pattern = "hrdina"
			elseif matches_any(ultimate, {"i", "í", "y", "e", "é", "ä"})
				or (TYPE == "name" and matches_any(ultimate, {"ü", "ö", "ő"}))
				or matches_any(word, {"Hrabě", "Poupě"})
			then
				pattern = "kuli"
			else
				pattern = "chlap"
			end
		elseif GENDER == "m-in" or GENDER == "m-anml" then
			if matches_any(ultimate, {"y", "ý", "i", "í"})
				or (matches_any(penultimate .. ultimate, {"ov", "in"}) and ends_with(genitive, "ho"))
			then
				pattern = "adjective"
			elseif (soft_consonants_set[ultimate]
				or (matches_any(ultimate, {"r", "l"}) and penultimate == "e"
					and (ends_with(genitive, "bra")))
				or ends_with_any(word, {"ár", "iar", "ier"})
				or matches_any(word, stroj_exceptions_pos))
				and not matches_any(word, stroj_exceptions_neg)
			then
				pattern = "stroj"
			else
				pattern = "dub"
			end
		elseif GENDER == "f" then
			if matches_any(word, {"gazdiná", "švagriná", "testiná", "ujčiná", "stryná",
				"kňažná", "kráľovná", "cisárovná", "cárovná", "šľachtičná", "princezná"
			}) then
				pattern = "gazdiná"
			elseif ultimate == "a" and not ends_with(genitive, "ej") then
				if soft_consonants_set[penultimate] or matches_any(penultimate, {"i", "y"}) or matches_any(word, {"rozopra", "konopa", "večera"}) then
					pattern = "ulica"
				else
					pattern = "žena"
				end
			elseif matches_any(ultimate, {"a", "á"})
				or (matches_any(penultimate .. ultimate, {"ova", "ina"}) and ends_with(genitive, "ej"))	
			then
				pattern = "adjective"
			elseif ends_with(word, "pani") or matches_any(word, {"Mať", "mať", "mater", "mati"}) then
				pattern = "irregular"
			else
				if not dlan_exceptions_neg[word]
					and (dlan_endings[ultimate] or dlan_exceptions_pos[word]
						or dlan_end_with_r_t[word] or (penultimate == "š" and ultimate == "ť" and not is_capital(first)))
				then
					pattern = "dlaň"
				else
					pattern = "kosť"
				end
			end
			
			if genitive then
				local g_ultimate = get_last_char(genitive)
				if g_ultimate == "y" then
					pattern = "žena"
				elseif g_ultimate == "i" then
					pattern = "kosť"
				end
			end
		elseif GENDER == "n" then
			if (ultimate == "o" or (penultimate == "u" and ultimate == "m")
				or (penultimate == "o" and ultimate == "n")
				or (penultimate == "m" and ultimate == "ä"))
				and not ends_with(genitive, "ho")
			then
				pattern = "mesto"
			elseif (penultimate == "i" and ultimate == "e")
				or ultimate == "í"
			then
				pattern = "vysvedčenie"
			elseif (ultimate == "e" or ultimate == "ě")
				and not ends_with(genitive, "ho")
			then
				pattern = "srdce"
			elseif matches_any(ultimate, {"a", "ä"}) then
				pattern = "dievča"
			elseif matches_any(ultimate, {"e", "é"})
				or matches_any(penultimate .. ultimate, {"ovo", "ino"})
			then
				pattern = "adjective"
			else
				pattern = "indeclinable"
			end
		end
	end
	
	return pattern
end

function append_ending(stem1, ending)
	if matches_any(get_first_char(ending), {"e", "i", "é", "í"}) then
		return harden_last_consonant(stem1) .. ending
	else
		return stem1 .. ending
	end
end

function append_endings(forms, word, stem, end2, end3, end4, end5, end6, end7, gen_pl, end9, end10, end11, end12)
	forms["nom_sg"] = word
	forms["gen_sg"] = append_ending(stem, end2)
	forms["dat_sg"] = append_ending(stem, end3)
	if GENDER == "m-pr"
		or matches_any(PATTERN, {"žena", "ulica", "gazdiná", "irregular"}) then
		forms["acc_sg"] = append_ending(stem, end4)
	else
		forms["acc_sg"] = word
	end
	forms["loc_sg"] = append_ending(stem, end5)
	forms["ins_sg"] = append_ending(stem, end6)
	
	local nom_pl = stem
	if ends_with_any(stem, {"k", "ch"}) and matches_any(GENDER, {"m-pr", "m-anml"}) and end7 == "i" then
		if ends_with(stem, "k") then
			nom_pl = remove_suffix(stem, "k") .. "c"
		else
			nom_pl = remove_suffix(stem, "ch") .. "s"
		end
	end
	forms["nom_pl"] = append_ending(nom_pl, end7)
	forms["gen_pl"] = gen_pl
	forms["dat_pl"] = append_ending(stem, end9)
	if GENDER == "m-pr" or PATTERN == "irregular" or end10 then
		forms["acc_pl"] = append_ending(stem, end10)
	else
		forms["acc_pl"] = forms["nom_pl"]
	end
	forms["loc_pl"] = append_ending(stem, end11)
	forms["ins_pl"] = append_ending(stem, end12)
end

function set_forms(forms, indices, values)
    for i = 1, #indices do
        forms[indices[i]] = values[i]
    end
end

function switch_plural_forms(forms)
    local indices = {"nom_pl", "gen_pl", "dat_pl", "acc_pl", "loc_pl", "ins_pl"}
    for _, index in ipairs(indices) do
        forms[index], forms[index .. "_alt"] = forms[index .. "_alt"], forms[index]
    end
end

function unset_alt_forms(forms)
    for key in pairs(forms) do
        if mw.ustring.find(key, "_alt$") then
            forms[key] = nil
        end
    end
end

function append_alternative_singular(forms, form2, form3, form4, form5, form6)
	forms["gen_sg2"] = (forms["gen_sg"] ~= form2) and form2
	forms["dat_sg2"] = (forms["dat_sg"] ~= form3) and form3
	if matches_any(GENDER, {"m-pr", "m-anml"})
		or matches_any(PATTERN, {"žena", "ulica", "gazdiná", "irregular"}) then
		forms["acc_sg2"] = (forms["acc_sg"] ~= form4) and form4
	end
	forms["loc_sg2"] = (forms["loc_sg"] ~= form5) and form5
	forms["ins_sg2"] = (forms["ins_sg"] ~= form6) and form6
end

function append_alternative_plural(forms, form1, form2, form3, form4, form5, form6)
	forms["nom_pl2"] = (forms["nom_pl"] ~= form2) and form1
	forms["gen_pl2"] = (forms["gen_pl"] ~= form2) and form2
	forms["dat_pl2"] = (forms["dat_pl"] ~= form2) and form3
	if form4 ~= nil and forms["acc_pl"] ~= form4 then
		forms["acc_pl2"] = form4
	elseif forms["acc_pl"] ~= form1 then
		forms["acc_pl2"] = form1
	end
	forms["loc_pl2"] = (forms["loc_pl"] ~= form2) and form5
	forms["ins_pl2"] = (forms["ins_pl"] ~= form2) and form6
end

function append_second_plural(forms, form1, form2, form3, form4, form5, form6)
	forms["nom_pl_alt"] = form1
	forms["gen_pl_alt"] = form2
	forms["dat_pl_alt"] = form3
	if form4 ~= nil then
		forms["acc_pl_alt"] = form4
	else
		forms["acc_pl_alt"] = forms["nom_pl_alt"]
	end
	forms["loc_pl_alt"] = form5
	forms["ins_pl_alt"] = form6
end

function append_animal_singular(forms, stem)
	set_forms(forms,
		{"gen_sg", "dat_sg", "acc_sg", "loc_sg"},
		{
			append_ending(stem, "a"),
			append_ending(stem, "ovi"),
			append_ending(stem, "a"),
			append_ending(stem, "ovi")
		}
	)
end

function get_last_char(str)
    local last = mw.ustring.sub(str, -1, -1)
    return last
end

function get_first_char(str)
    local first = mw.ustring.sub(str, 1, 1)
    return first
end

function remove_last_char(str)
    local stem = mw.ustring.sub(str, 1, -2)
    return stem
end

function is_capital(str)
	return mw.ustring.find(str, "[A-Z]")
end

function get_last_letters(word)
	local ultimate = get_last_char(word)
	local penultimate = get_last_char(remove_last_char(word))
	local antepenultimate = get_last_char(remove_last_char(remove_last_char(word)))
	
	if (penultimate == "c" and ultimate == "h")
		or (penultimate == "d" and (ultimate == "z" or ultimate == "ž"))
	then
		ultimate = penultimate .. ultimate
		penultimate = antepenultimate
		antepenultimate = get_last_char(remove_last_char(remove_last_char(remove_last_char(word))))
	end
	
	if (antepenultimate == "c" and penultimate == "h")
		or (antepenultimate == "d" and (penultimate == "z" or penultimate == "ž"))
	then
		penultimate = antepenultimate .. penultimate
	end
	
	if penultimate == "v" and consonants_set[ultimate] and not matches_any(ultimate, {"r", "l", "ŕ", "ĺ"}) then
		penultimate = "ʋ"
	end
	
	return ultimate, penultimate
end

function ends_with(word, suffix)
    -- Check if suffix matches the end of word
    word = (word ~= nil) and word or ""
    return mw.ustring.find(word, suffix .. "$") ~= nil
end

function ends_with_any(word, suffixes)
    for _, suffix in ipairs(suffixes) do
        if ends_with(word, suffix) then
            return true  -- Return true if any suffix matches
        end
    end
    return false  -- Return false if no suffix matches
end

function matches_any(value, list)
    for _, item in ipairs(list) do
        if value == item then
            return true
        end
    end
    return false
end

function get_vowel(syllable)
    -- Define patterns for diphthongs and vowels
    local diphthong_pattern = "ia|ie|iu|ô"
    local vowel_pattern = "[aeiouyáéíóúýäöőüű]"
    local syllabic_consonant_pattern = "[rl]"

    -- Check for a diphthong first
    local diphthong = mw.ustring.match(syllable, diphthong_pattern)
    if diphthong then
        return diphthong
    end

    -- Check for a single vowel
    local vowel = mw.ustring.match(syllable, vowel_pattern)
    if vowel then
        return vowel
    end

    -- Check for a syllabic consonant if no vowel is found
    local syllabic_consonant = mw.ustring.match(syllable, syllabic_consonant_pattern)
    if syllabic_consonant then
        return syllabic_consonant
    end

    return nil  -- Return nil if no vowel or syllabic consonant is found
end

-- Function to split a word into characters and bigraphs
function split_into_letter_units(word)
    local units = {}
    local i = 1
    local length = mw.ustring.len(word)

    while i <= length do
        local two_char = mw.ustring.sub(word, i, i + 1)
        
        -- Check if the two-character sequence is a bigraph or diphthong
        if bigraphs_set[two_char] or diphthongs_set[two_char] then
            table.insert(units, two_char)
            i = i + 2
        else
            -- If not a bigraph/diphthong, treat it as a single character
            table.insert(units, mw.ustring.sub(word, i, i))
            i = i + 1
        end
    end
    
    return units
end

-- check if a unit is a vowel or syllabic element
function is_vowel(unit, prev_unit, next_unit)
    if diphthongs_set[unit] then return true end
    if vowels_set[unit] then return true end
    
    -- Check if 'r' or 'l' are syllabic (preceded and followed by consonants)
    if (unit == "r" or unit == "l") and prev_unit and next_unit and not vowels_set[prev_unit] and not vowels_set[next_unit] then
        return true
    end
    
    return false
end

-- Function to split a word into syllables according to Slovak rules
function split_into_syllables(word)
    local units = split_into_letter_units(word)
    local syllables = {}
    local current_syllable = ""
    local i = 1
    local length = #units
    local first_vowel_found = false  -- Flag to indicate when the first vowel has been encountered

    -- Iterate over the units in the word
    while i <= length do
        local unit = units[i]
        local next_unit = i < length and units[i + 1] or nil
        local is_current_vowel = is_vowel(unit, units[i - 1], next_unit)
        
        -- If we haven't encountered the first vowel, keep adding to the first syllable
        if not first_vowel_found then
            current_syllable = current_syllable .. unit
            if is_current_vowel then
                first_vowel_found = true  -- Mark that the first vowel has been found
            end
        else
            if is_current_vowel then
                -- If a vowel is encountered after the first vowel has been found, finalize the current syllable
                if current_syllable ~= "" and is_vowel(current_syllable, nil, nil) then
                    table.insert(syllables, current_syllable)
                    current_syllable = ""
                end
                current_syllable = current_syllable .. unit
            else
                -- Handling consonants between vowels
                local consonant_cluster = unit

                -- Collect any consecutive consonants into a cluster
                local j = i + 1
                while j <= length and not is_vowel(units[j], units[j - 1], units[j + 1]) do
                    consonant_cluster = consonant_cluster .. units[j]
                    j = j + 1
                end

                local consonant_count = mw.ustring.len(consonant_cluster)

                if next_unit and is_vowel(next_unit, unit, units[j]) then
                    -- Apply syllable rules based on the number of consonants in the cluster
                    if consonant_count == 1 then
                        -- Rule 3: Single consonant goes to the next syllable
                        table.insert(syllables, current_syllable)  -- End the current syllable without the consonant
                        current_syllable = consonant_cluster       -- Start the next syllable with the consonant
                    elseif consonant_count == 2 then
                        -- Rule 4: Two consonants split between syllables
                        current_syllable = current_syllable .. mw.ustring.sub(consonant_cluster, 1, 1)
                        table.insert(syllables, current_syllable)
                        current_syllable = mw.ustring.sub(consonant_cluster, 2, 2)
                    else
                        -- Rule 5: Three or more consonants - first goes with current syllable, rest with next
                        current_syllable = current_syllable .. mw.ustring.sub(consonant_cluster, 1, 1)
                        table.insert(syllables, current_syllable)
                        current_syllable = mw.ustring.sub(consonant_cluster, 2)
                    end
                    i = j - 1  -- Adjust the index to skip the processed consonants
                else
                    current_syllable = current_syllable .. unit
                end
            end
        end

        i = i + 1
    end

    -- Add any remaining characters as the final syllable
    if #current_syllable > 0 then
        table.insert(syllables, current_syllable)
    end

    -- Reverse the syllables array for the requested output order
    local reversed_syllables = {}
    for j = #syllables, 1, -1 do
        reversed_syllables[#reversed_syllables + 1] = syllables[j]
    end

    -- Return the count of syllables and the reversed syllable array
    return #syllables, reversed_syllables
end

function is_long(syllable)
    return mw.ustring.find(syllable, "[áéíýúôĺŕ]") ~= nil or mw.ustring.find(syllable, "i[aeu]") ~= nil
end

function is_short(syllable)
    return not is_long(syllable)
end

local function get_last_consonant_before_vowel(syllable)
    local vowel = get_vowel(syllable)
    
    -- If there is no vowel in the syllable, return false
    if not vowel then
        return false
    end

    -- Find the position of the vowel in the syllable
    local vowel_pos = mw.ustring.find(syllable, vowel)

    -- Loop backwards from the position of the vowel to find the last consonant
    for i = vowel_pos - 1, 1, -1 do
        local char = mw.ustring.sub(syllable, i, i)
        if not mw.ustring.find(char, "[aeiouáéíóúýôä]") then
            return char  -- Return the last consonant before the vowel
        end
    end

    return false  -- Return false if no consonant is found before the vowel
end

-- Function to lengthen the last vowel in a syllable if it’s not already long
function lengthen_vowel(syllable)
    if is_long(syllable) then
        return syllable  -- Return as-is if the syllable is already long
    end

    local lengthening_map = {
        ["a"] = "á", ["i"] = "í", ["y"] = "ý", ["u"] = "ú",
        ["ä"] = "ia", ["e"] = "ie", ["o"] = "ô"
    }
    
    local cons_map = {
    	["ď"] = "d", ["ť"] = "t", ["ň"] = "n", ["ľ"] = "l"
    }

    -- Check for regular vowels first and replace if found
    for vowel, long_vowel in pairs(lengthening_map) do
        if mw.ustring.find(syllable, vowel) then
        	-- if there is a soft consonant before "a", it becomes "ia" instead of "á"
        	local last_cons = get_last_consonant_before_vowel(syllable) or ""
        	if soft_consonants_set[last_cons] and vowel == "a" then
    			long_vowel = "ia"
    		end
        	
        	-- if a or ä changes into "ia", the previous consonant should be written as hard
        	if matches_any(vowel, {"ä", "a"}) and matches_any(last_cons, {"ď", "ť", "ň", "ľ"}) then
        		syllable = mw.ustring.gsub(syllable, last_cons .. vowel, cons_map[last_cons] .. vowel, 1)
        	end
        	
            syllable = mw.ustring.gsub(syllable, vowel, long_vowel, 1)
            return syllable  -- Return immediately after replacing a regular vowel
        end
    end

    -- Only replace "r" and "l" if no other vowels were found
    if not mw.ustring.find(syllable, "[aeiouyáéíóúýôä]") then
        syllable = mw.ustring.gsub(syllable, "r", "ŕ")
        syllable = mw.ustring.gsub(syllable, "l", "ĺ")
    end

    return syllable
end

-- Helper function to determine if the syllable ends with a consonant cluster
function ends_with_consonant_cluster(last_syllable)
    local last_char = get_last_char(last_syllable)
    local second_last_char = mw.ustring.sub(last_syllable, -2, -2)
    local third_last_char = mw.ustring.sub(last_syllable, -3, -3)
    local last_two_chars = mw.ustring.sub(last_syllable, -2)

    -- Check if the syllable contains any regular vowels
    local has_vowel = mw.ustring.find(last_syllable, "[aeiouyáéíóúýôä]")

    if has_vowel then
        -- If the last two characters form a digraph, check the third-last character for a cluster
        if bigraphs_set[last_two_chars] then
            return not vowels_set[third_last_char]
        else
            -- If no digraph, just check the last two characters
            return not vowels_set[last_char] and not vowels_set[second_last_char]
        end
    else
        -- No regular vowel; treat `r` or `l` as syllabic if either is in the last two characters
        if (last_char == "r" or last_char == "l") or (second_last_char == "r" or second_last_char == "l") then
            return false
        else
            -- No syllabic `r` or `l`, so both characters are treated as consonants
            return not vowels_set[last_char] and not vowels_set[second_last_char]
        end
    end
end

function remove_mobile_vowel(word)
    local units = split_into_letter_units(word)  -- Split word into units
    for i = #units, 1, -1 do
        local unit = units[i]
        if matches_any(unit, {"e", "ie", "o", "i", "á"}) then
            -- Remove the mobile vowel unit and reassemble the word
            table.remove(units, i)
            return table.concat(units)
        end
    end
    return word  -- Return the original word if no mobile vowel is found
end

function soften_last_consonant(str)

    local consonants_t = { ["c"] = "č", ["d"] = "ď", ["l"] = "ľ", ["n"] = "ň",
    	["s"] = "š", ["t"] = "ť", ["z"] = "ž" }
    local last_char = get_last_char(str)
    
    -- Check if the last character is in consonants_t and replace if needed
    return consonants_t[last_char] and remove_last_char(str) .. consonants_t[last_char] or str
end

function harden_last_consonant(str)
    local soft_to_hard = { ["ď"] = "d", ["ť"] = "t", ["ň"] = "n", ["ľ"] = "l" }
    local last_char = get_last_char(str)
    
    -- Check if the last character is a soft consonant and replace if needed
    return soft_to_hard[last_char] and remove_last_char(str) .. soft_to_hard[last_char] or str
end

function remove_suffix(form, suffix)
    if mw.ustring.find(form, suffix .. "$") then
        local length = mw.ustring.len(suffix)
        return mw.ustring.sub(form, 1, -length-1)
    end

    return form
end

function pattern_link(pattern)
	return "''[[Appendix:Slovak declension pattern " .. pattern .. "|" .. pattern .. "]]''"
end

function decline_with_more_stems(genitive)
	local genitives = {}
	for part in mw.ustring.gmatch(genitive, "[^/]+") do
	    table.insert(genitives, part)
	end
	
	local forms, title = declensions[PATTERN](PAGENAME, genitives[1])
	normalize_forms(forms)
	local forms2, title2 = declensions[PATTERN](PAGENAME, genitives[2])
	normalize_forms(forms2)
	
	append_alternative_singular(forms, forms2["gen_sg"], forms2["dat_sg"], forms2["acc_sg"],
		forms2["loc_sg"], forms2["ins_sg"])
	append_alternative_plural(forms, forms2["nom_pl"], forms2["gen_pl"], forms2["dat_pl"],
		forms2["acc_pl"], forms2["loc_pl"], forms2["ins_pl"])
	
	return forms, title
end

function split_into_units(expression, genitive)
    -- Split the original expression into words
    local words = {}
    for word in mw.ustring.gmatch(expression, "%S+") do
        table.insert(words, word)
    end

    -- Split the genitive phrase into words, if provided
    local gen_words = {}
    if genitive then
        for word in mw.ustring.gmatch(genitive, "%S+") do
            table.insert(gen_words, word)
        end
        -- Check if the genitive phrase has the same structure as the original
        if #gen_words ~= #words then
            error("Genitive phrase must have the same number of words as the original expression.")
        end
    end

    -- Combine words into units, grouping prepositional phrases for both expressions
    local units = {}
    local gen_units = {}
    local i = 1
    while i <= #words do
        if prepositions_set[words[i]] and words[i + 1] then
            -- Combine preposition with the following words as one unit
            local phrase = words[i]
            local gen_phrase = genitive and gen_words[i] or nil
            
            i = i + 1
            while i <= #words do
                phrase = phrase .. " " .. words[i]
                if genitive then
                    gen_phrase = gen_phrase .. " " .. gen_words[i]
                end
                if i == #words then
                    table.insert(units, phrase)
                    if genitive then table.insert(gen_units, gen_phrase) end
                end
                i = i + 1
            end
        else
            -- Add standalone word as a unit
            table.insert(units, words[i])
            if genitive then
                table.insert(gen_units, gen_words[i])
            end
            i = i + 1
        end
    end

    -- Return both units and genitive units
    return units, gen_units
end

function generate_combined_forms(units, gen_units)
	-- Process each unit to generate forms
    local all_forms = {}
    local patterns = ""
    for i = 1, #units do
	    local unit = units[i]
	    local gen_unit = gen_units[i]
        if conjunctions_set[unit] or unit == gen_unit then
            -- Conjunctions are indeclinable
            local forms = declensions["indeclinable"](unit, gen_unit)
            table.insert(all_forms, forms)
        else
            -- Decline each unit using determine_pattern and declensions
            local pattern = determine_pattern(unit, gen_unit)
            local forms, title = declensions[pattern](unit, gen_unit)
        	normalize_forms(forms)
        	table.insert(all_forms, forms)
        end
    end
    
    -- Combine forms into a single forms table
    local combined_forms = {}
    local cases = {"nom", "gen", "dat", "acc", "loc", "ins"}
    local numbers = {"sg", "pl"}
    
    -- check if vocative and plural 2 forms are necessary
    for _, forms in ipairs(all_forms) do
	    if forms["voc_sg"] and #cases == 6 then
	        table.insert(cases, "voc")
	    end
	    if forms["nom_pl_alt"] and #numbers == 2 then
	        table.insert(numbers, "pl_alt")
	    end
	end

    for _, case in ipairs(cases) do
        for _, number in ipairs(numbers) do
            local form_key = case .. "_" .. number
            local combined_form, highest_index = {}, 1

            -- Gather primary and numbered alternative forms (e.g., gen_sg2, gen_sg3)
            for _, forms in ipairs(all_forms) do
                local primary_form = forms[form_key] or forms["nom_" .. number] or forms[case .. "_pl"]
                table.insert(combined_form, primary_form)

                -- Check the highest alternative index
                for alt_index = 2, 4 do
                    local alt_key = form_key .. alt_index
                    if forms[alt_key] then
                        highest_index = alt_index
                    end
                end
            end

            -- Combine primary forms
            combined_forms[form_key] = mw.ustring.gsub(table.concat(combined_form, " "), "^%s+", "")

            -- Combine alternative forms if present
            if highest_index > 1 then
            	for alt_index = 2, highest_index do
            		-- Check and add numbered alternative forms
            		local alt_key = form_key .. alt_index
            		alt_form = {}
	                for _, forms in ipairs(all_forms) do
	                	local primary_form = forms[form_key] or forms["nom_" .. number]
	                    if forms[alt_key] then
	                        table.insert(alt_form, forms[alt_key])
	                    else
	                    	table.insert(alt_form, primary_form)
	                    end
	                end
	                combined_forms[alt_key] = mw.ustring.gsub(table.concat(alt_form, " "), "^%s+", "")
	            end
	        end
        end
    end
    
    return combined_forms
end

function normalize_forms(forms)
    -- Step 1: Remove forms based on NUMBER
    if NUMBER == "sg" then
        for key in pairs(forms) do
            if mw.ustring.find(key, "_pl") then
                forms[key] = nil  -- Remove plural forms if "n" is "sg"
            end
        end
    elseif NUMBER == "pl" then
        for key in pairs(forms) do
            if mw.ustring.find(key, "_sg") then
                forms[key] = nil  -- Remove singular forms if "n" is "pl"
            end
        end
    end

    -- Step 2: Ensure all mandatory indices are set
    local cases = {"nom", "gen", "dat", "acc", "loc", "ins"}
    local numbers = {"sg", "pl"}
    for _, case in ipairs(cases) do
        for _, number in ipairs(numbers) do
            local form_key = case .. "_" .. number
            if not forms[form_key] then
                forms[form_key] = "&mdash;"  -- Set missing forms to &mdash;
            end
        end
    end

    -- Step 3: Create missing _pl_alt indices if at least one exists
    local has_alt_plural = false
    for key in pairs(forms) do
        if mw.ustring.find(key, "_pl_alt") then
            has_alt_plural = true
            break
        end
    end
    if has_alt_plural then
        for _, case in ipairs(cases) do
            local form_key_alt = case .. "_pl_alt"
            if not forms[form_key_alt] then
                forms[form_key_alt] = "&mdash;"  -- Create missing _pl_alt forms
            end
        end
    end

    -- Step 4: Create vocative plural forms if voc_sg exists
    if forms["voc_sg"] then
        forms["voc_pl"] = forms["nom_pl"]  -- Set voc_pl to nom_pl
        if has_alt_plural then
            forms["voc_pl_alt"] = forms["nom_pl_alt"]  -- Set voc_pl_alt to nom_pl_alt if it exists
        end
    end
end

function specified_by_user(forms, args)
	local cases = {nom = true, gen = true, dat = true, acc = true, voc = true, loc = true, ins = true}
	local numbers = {sg = true, pl = true}
	
	for key, value in pairs(args) do
	    -- Match the pattern "<case>_<number><optional digit>" using mw.ustring.match
	    local case, number, optional_digit = mw.ustring.match(key, "^(%a%a%a)_(%a%a)(%d?)$")
	    
	    -- Check if it matches the cases and numbers you want
	    if case and cases[case] and number and numbers[number] then
	        forms[key] = make_link(value, case .. "|" .. get_first_char(number))
	    end
	end
end

function make_link(link, accel_form)
    local new_link = link

    -- If link is not empty, valid, and not "&mdash;", create the full link
    if link ~= "" and link and link ~= "&mdash;" then
        new_link = m_links.full_link({lang = lang, term = link, accel = {form = accel_form}})
    end

    return new_link
end

function make_table_header(title)
    local header = [=[<div class="NavFrame" style="max-width:50em">
    <div class="NavHead">]=] .. title .. [=[</div>
    <div class="NavContent">
    <table style="text-align:center" class="inflection-table">]=]

    return header
end

function make_simple_row(forms, case)
    local case_code = mw.ustring.sub(case, 1, 3)
    local row = "<tr><td style=\"background:#eff7ff\">'''" .. case .. "'''</td>"
    
    -- Singular
    if NUMBER ~= "pl" then
	    row = row .. "<td><span lang=\"sk\">" .. make_link(forms[case_code .. "_sg"], case_code .. "|s") .. "</span>"
	    
	    -- Loop to check and display secondary singular forms in the same cell
	    for i = 2, 4 do
	        local form_key = case_code .. "_sg" .. i
	        if not forms[form_key] then
	            break  -- Exit loop if form doesn't exist
	        end
	        row = row .. ",<br /><span lang=\"sk\">" .. make_link(forms[form_key], case_code .. "|s") .. "</span>"
	    end
	    row = row .. "</td>"
	end

    -- Plural
    if NUMBER ~= "sg" then
    	-- Primary plural form
	    row = row .. "<td><span lang=\"sk\">" .. make_link(forms[case_code .. "_pl"], case_code .. "|p") .. "</span>"
	
	    -- Loop to check and display secondary plural forms in the same cell
	    for i = 2, 4 do
	        local form_key = case_code .. "_pl" .. i
	        if not forms[form_key] then
	            break  -- Exit loop if form doesn't exist
	        end
	        row = row .. ",<br /><span lang=\"sk\">" .. make_link(forms[form_key], case_code .. "|p") .. "</span>"
	    end
	    row = row .. "</td>"
	
	    -- Check for alternative plural and add it in a separate cell
	    if forms[case_code .. "_pl_alt"] then
	        row = row .. "<td><span lang=\"sk\">" .. make_link(forms[case_code .. "_pl_alt"], case_code .. "|p") .. "</span></td>"
	    end
	end

    row = row .. "</tr>"

    return row
end

function make_table_header2(forms)
	local tr_open = "<tr><th style=\"background:#d9ebff; width: 10em;\"></th>"
	local singular = "<th style=\"background:#d9ebff\">singular</th>"
	local plural = "<th style=\"background:#d9ebff\">plural</th>"
	local plural1 = "<th style=\"background:#d9ebff\">plural 1</th>"
	local plural2 = "<th style=\"background:#d9ebff\">plural 2</th>"
	local tr_close = "</tr>"
	
	local alt_plural = false
	if forms["nom_pl_alt"] or forms["gen_pl_alt"] or forms["dat_pl_alt"]
		or forms["acc_pl_alt"] or forms["loc_pl_alt"] or forms["ins_pl_alt"]
	then
		alt_plural = true
	end
	
	local header
	
	if NUMBER == "sg" then
		header = tr_open .. singular .. tr_close
	elseif NUMBER == "pl" then
		if alt_plural then
			header = tr_open .. plural1 .. plural2 .. tr_close
		else
			header = tr_open .. plural .. tr_close
		end
	elseif alt_plural then
		header = tr_open .. singular .. plural1 .. plural2 .. tr_close
	else
		header = tr_open .. singular .. plural .. tr_close
	end

    return header
end

function make_table_footer()
    return "</table></div></div>"
end
 
-- Make the table
function make_table(forms, title)
    for key, form in pairs(forms) do
        -- check for empty strings and nil's
        if form == "" or not form then
            forms[key] = "&mdash;"
        end
    end

    local final = make_table_header(title)
    final = final .. make_table_header2(forms)
    final = final .. make_simple_row(forms, "nominative")
    final = final .. make_simple_row(forms, "genitive")
    final = final .. make_simple_row(forms, "dative")
    final = final .. make_simple_row(forms, "accusative")
    if forms["voc_sg"] then
    	final = final .. make_simple_row(forms, "vocative")
    end
    final = final .. make_simple_row(forms, "locative")
    final = final .. make_simple_row(forms, "instrumental")

    final = final .. make_table_footer()
 
    return final
end
 
return export