local export = {}

local force_cat = false -- 테스트용

local m_data = mw.loadData("Module:IPA/data")
local m_str_utils = require("Module:string utilities")
local m_symbols = mw.loadData("Module:IPA/data/symbols")
local pron_qualifier_module = "Module:pron qualifier"
local qualifier_module = "Module:qualifier"
local references_module = "Module:references"
local syllables_module = "Module:syllables"
local utilities_module = "Module:utilities"
local m_syllables -- [[Module:syllables]]; 필요시 로드됨

local concat = table.concat
local find = string.find
local gmatch = m_str_utils.gmatch
local gsub = string.gsub
local insert = table.insert
local len = m_str_utils.len
local listToText = mw.text.listToText
local match = string.match
local sub = string.sub
local u = m_str_utils.char
local ufind = m_str_utils.find
local ugsub = m_str_utils.gsub
local umatch = m_str_utils.match
local usub = m_str_utils.sub

local namespace = mw.title.getCurrentTitle().namespace
local is_content_page = namespace == 0 or namespace == 118

local function track(page)
	require("Module:debug/track")("IPA/" .. page)
	return true
end

local function process_maybe_split_categories(split_output, categories, prontext, lang, errtext)
	if split_output ~= "raw" then
		if categories[1] then
			categories = require(utilities_module).format_categories(categories, lang, nil, nil, force_cat)
		else
			categories = ""
		end
	end
	if split_output then -- IPA를 링크 등에서 사용할 때
		if errtext then
			return prontext, categories, errtext
		else
			return prontext, categories
		end
	else
		return prontext .. (errtext or "") .. categories
	end
end

--[==[
여러 IPA 발음 라인을 {{tl|IPA}}처럼 포맷합니다. 즉, {"IPA:"}로 시작하여 언어의 음운론을 설명하는 부록 페이지로
링크되는 {"key"} 단어가 뒤따르고, 해당 언어의 발음이 포함된 용어에 대한 범주가 추가됩니다
({{cd|<var>lang</var> IPA 발음이 포함된 낱말}}). 추가 텍스트와 범주를 제외하면, 이는 {format_IPA_multiple()}와
동일하며, 해당 문서에 설명된 고려사항이 여기에도 적용됩니다. 단일 매개변수 `data`는 다음 필드를 포함하는 객체입니다:
* `lang`: 발음의 언어를 나타내는 객체로, 유효하지 않은 음소가 포함된 발음에 대한 정리 범주를 추가하는 데 사용됩니다.
  또한 특정 언어의 음절 수를 파악하여 [[:분류:이탈리아어 2음절 낱말]]와 같은 범주를 추가하는 데 사용됩니다.
  또한 발음에 대한 범주를 추가하고, 범주에 대한 올바른 정렬 키를 결정하는 데 사용됩니다. {format_IPA_multiple()}와
  달리 `lang`은 {nil}이 될 수 없습니다.
* `items`: {format_IPA_multiple()}와 동일한 형식으로 발음 목록.
* `err`: {nil}이 아닌 경우, 언어의 음운론에 대한 링크 대신 사용할 오류 메시지 문자열.
* `separator`: 형식화된 항목을 구분할 때 사용할 기본 구분 기호. 기본값은 {", "}입니다. 첫 번째 항목에는
  기본 구분 기호가 항상 빈 문자열로 설정됩니다. `items`의 항목별 `separator` 필드로 재정의됩니다.
* `sort_key`: 범주에 사용할 명시적 정렬 키.
* `no_count`: [[:분류:이탈리아어 2음절 낱말]]와 같은 {#-음절 낱말} 범주 추가 억제.
  이는 특정 언어에만 적용되며, 해당 언어의 음운론에 따라 음절 수를 계산할 수 있는 경우에만 적용됩니다.
  또한 정리 또는 기타 범주의 추가를 억제하지 않습니다. 억제가 필요한 경우 `split_output`을 사용하여
  범주를 별도로 반환하고 무시하십시오.
* `split_output`: 지정되지 않은 경우 반환 값은 형식화된 발음과 형식화된 범주의 연결입니다. 그렇지 않으면 두
  값이 반환됩니다: 형식화된 발음과 범주. `split_output` 값이 {"raw"}인 경우, 범주는 목록 형식으로 반환되며,
  목록 요소는 [[Module:utilities]]의 {format_categories()}에 전달할 수 있는 범주 문자열과 범주 객체의
  조합입니다. `split_output` 값이 {nil}이 아닌 경우, 범주는 미리 형식화된 연결 문자열로 반환됩니다.
* `include_langname`: 지정된 경우 결과에 언어 이름을 접두사로 추가하고, 그 뒤에 콜론을 추가합니다.
* `q`: {nil} 또는 형식화된 발음 앞에 표시할 왼쪽 설명자 목록 ({{tl|q}}처럼).
* `qq`: {nil} 또는 모든 형식화된 발음 뒤에 표시할 오른쪽 설명자 목록.
* `a`: {nil} 또는 형식화된 발음 앞에 표시할 왼쪽 악센트 설명자 목록 ({{tl|a}}처럼).
* `aa`: {nil} 또는 모든 형식화된 발음 뒤에 표시할 오른쪽 악센트 설명자 목록.
]==]
function export.format_IPA_full(data)
	if type(data) ~= "table" or data.getCode then
		error("이제 format_IPA_full()에 인수 테이블을 제공해야 합니다. 첫 번째 인수는 그 테이블이어야 하며 언어 객체가 아닙니다.")
	end
	local lang = data.lang
	local items = data.items
	local err = data.err
	local separator = data.separator
	local sort_key = data.sort_key
	local no_count = data.no_count
	local split_output = data.split_output
	local q = data.q
	local qq = data.qq
	local a = data.a
	local aa = data.aa
	local include_langname = data.include_langname

	local hasKey = m_data.langs_with_infopages

	if not lang or not lang.getCode then
		error("format_IPA_full()에 언어를 지정해야 합니다.")
	end
	local langname = lang:getCanonicalName() or lang:getCanonicalName2()

	local prefix_text
	if err then
		prefix_text = '<span class="error">' .. err .. '</span>'
	else
		if hasKey[lang:getCode()] then
			prefix_text = "부록:" .. langname .. " 발음"
		else
			prefix_text = "w:" .. langname .. " 음운론"
		end
		prefix_text = "[[" .. prefix_text .. "|표기]]"
	end

	local prefix = "[[w:국제 음성 기호|IPA]]<sup>(" .. prefix_text .. ")</sup>:&#32;"

	local IPAs, categories = export.format_IPA_multiple(lang, items, separator, no_count, "raw")

	if is_content_page then
		insert(categories, {
			cat = langname .. " IPA 발음이 포함된 낱말",
			sort_key = sort_key
		})
	end

	local prontext = prefix .. IPAs
	if q and q[1] or qq and qq[1] or a and a[1] or aa and aa[1] then
		prontext = require(pron_qualifier_module).format_qualifiers {
			lang = lang,
			text = prontext,
			q = q,
			qq = qq,
			a = a,
			aa = aa,
		}
	end
	if include_langname then
		prontext = langname .. ": " .. prontext
	end
	return process_maybe_split_categories(split_output, categories, prontext, lang)
end

local function split_phonemic_phonetic(pron)
	local reconstructed, phonemic, phonetic = match(pron, "^(%*?)(/.-/)%s+(%[.-%])$")
	if reconstructed then
		return reconstructed .. phonemic, reconstructed .. phonetic
	else
		return pron, nil
	end
end

local function determine_repr(pron)
	local repr_mark = {}
	local repr, reconstructed

	-- 일부 재구성 페이지에서 사용되는, 표현 표시 앞의 초기 별표 제거
	if sub(pron, 1, 1) == "*" then
		reconstructed = true
		pron = sub(pron, 2)
	end

	local representation_types = {
		['/'] = { right = '/', type = 'phonemic', },
		['['] = { right = ']', type = 'phonetic', },
		['⟨'] = { right = '⟩', type = 'orthographic', },
		['-'] = { type = 'rhyme' },
	}

	repr_mark.i, repr_mark.f, repr_mark.left, repr_mark.right = ufind(pron, '^(.).-(.)$')

	local representation_type = representation_types[repr_mark.left]

	if representation_type then
		if representation_type.right then
			if repr_mark.right == representation_type.right then
				repr = representation_type.type
			end
		else
			repr = representation_type.type
		end
	else
		repr = nil
	end

	return repr, reconstructed
end

local function hasInvalidSeparators(transcription)
	if match(transcription, "%.\203[\136\140]") then -- [ˈˌ]
		return true
	else
		return false
	end
end

--[==[
여러 발음의 라인을 포맷합니다 (즉, {"IPA:"} 없이 카테고리 {{cd|<var>lang</var> IPA 발음이 포함된 낱말}}에 추가하지 않고).
개별 발음은 {format_IPA()}를 사용하여 형식화되고, 구분 기호, 설명자, 전-후 텍스트 등과 결합되어 발음 라인을 형성합니다.
허용되는 매개변수는 다음과 같습니다:
* `lang`은 발음의 언어를 나타내는 객체로, 유효하지 않은 음소가 포함된 발음에 대한 정리 범주를 추가하는 데 사용됩니다.
  특정 언어의 음절 수를 파악하여 [[:분류:이탈리아어 2음절 낱말]]와 같은 범주를 추가하는 데 사용됩니다.
  `lang`은 {nil}일 수 있습니다.
* `items`는 각 발음이 다음 속성을 가진 객체인 발음 목록입니다:
** `pron`: {format_IPA()}에서 허용하는 형식의 발음 (즉, 음운적 {/.../}, 음성적 {[...]} 또는 철자적 {⟨...⟩}).
** `pretext`: 설명자나 악센트 설명자 안에 형식화된 발음 바로 앞에 표시할 텍스트.
** `posttext`: 설명자나 악센트 설명자 안에 형식화된 발음 바로 뒤에 표시할 텍스트.
** `q` 또는 `qualifiers`: {nil} 또는 형식화된 발음 앞에 표시할 왼쪽 설명자 목록 ({{tl|q}}처럼).
** `qq`: {nil} 또는 형식화된 발음 뒤에 표시할 오른쪽 설명자 목록.
** `a`: {nil} 또는 형식화된 발음 앞에 표시할 왼쪽 악센트 설명자 목록 ({{tl|a}}처럼).
** `aa`: {nil} 또는 형식화된 발음 뒤에 표시할 오른쪽 악센트 설명자 목록.
** `refs`: {nil} 또는 발음과 모든 후 텍스트 및 설명자 뒤에 추가할 참조 또는 참조 사양 목록; 목록 항목의 값은
  참조 텍스트 (일반적으로 {{tl|cite-book}}과 같은 인용 템플릿 호출 또는 그러한 호출을 래핑하는 템플릿)를 포함하는
  문자열이거나, 필드 `text` (참조 텍스트), `name` (참조의 이름, 예: {{cd|<nowiki><ref name="foo">...</ref></nowiki>}} 또는
  {{cd|<nowiki><ref name="foo" /></nowiki>}}), 및/또는 `group` (참조 그룹, 예: {{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} 또는
  {{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}})를 포함하는 객체일 수 있습니다; 이는 참조를 적절하게 형식화하고
  실제 참조로 하이퍼링크되는 각주 번호를 삽입하는 파서 함수.
** `gloss`: {nil} 또는 항목에 대한 정의 (다른 정의에 다른 발음이 있는 경우).
** `pos`: {nil} 또는 항목에 대한 품사 (다른 품사에 다른 발음이 있는 경우).
** `separator`: 형식화된 발음 및 모든 설명자, 악센트 설명자 및 전-후 텍스트 바로 앞에 삽입할 구분 텍스트;
   기본값은 외부 `separator` 매개변수입니다.
* `separator`: 형식화된 항목을 구분할 때 사용할 기본 구분 기호. 기본값은 {", "}입니다. 첫 번째 항목에는
  기본 구분 기호가 항상 빈 문자열로 설정됩니다. `items`의 항목별 `separator` 필드로 재정의됩니다.
* `no_count`: [[:분류:이탈리아어 2음절 낱말]]와 같은 {#-음절 낱말} 범주 추가 억제.
  이는 특정 언어에만 적용되며, 해당 언어의 음운론에 따라 음절 수를 계산할 수 있는 경우에만 적용됩니다.
  또한 정리 또는 기타 범주의 추가를 억제하지 않습니다. 억제가 필요한 경우 `split_output`을 사용하여
  범주를 별도로 반환하고 무시하십시오.
* `split_output`: 지정되지 않은 경우 반환 값은 형식화된 발음과 형식화된 범주의 연결입니다. 그렇지 않으면 두
  값이 반환됩니다: 형식화된 발음과 범주. `split_output` 값이 {"raw"}인 경우, 범주는 목록 형식으로 반환되며,
  목록 요소는 [[Module:utilities]]의 {format_categories()}에 전달할 수 있는 범주 문자열과 범주 객체의
  조합입니다. `split_output` 값이 {nil}이 아닌 경우, 범주는 미리 형식화된 연결 문자열로 반환됩니다.
]==]
function export.format_IPA_multiple(lang, items, separator, no_count, split_output)
	local categories = {}
	separator = separator or ", "

	if not lang then
		track("format-multiple-nolang")
	end

	-- 형식화
	if not items[1] then
		if namespace == 10 then -- 템플릿
			insert(items, {pron = "/aɪ piː ˈeɪ/"})
		else
			insert(categories, "발음이 존재하지 않는 발음 틀")
		end
	end

	local bits = {}

	for i, item in ipairs(items) do
		local bit

		-- 발음이 완전히 비어 있는 경우 이를 허용하고 아무 작업도 수행하지 않습니다. 이렇게 하면
		-- 전 텍스트 및/또는 후 텍스트를 지정하여 발음 대신 ''unknown''과 같은 내용을 표시하도록 할 수 있습니다.
		-- (예: [[Module:ca-IPA]]에서 ?가 철자로 사용될 때처럼; 예제는 [[guèiser]] 참조).
		if item.pron == "" then
			bit = ""
		else
			local item_categories, errtext
			bit, item_categories, errtext = export.format_IPA(lang, item.pron, "raw")
			bit = bit .. errtext
			for _, cat in ipairs(item_categories) do
				insert(categories, cat)
			end
		end

		if item.pretext then
			bit = item.pretext .. bit
		end

		if item.posttext then
			bit = bit .. item.posttext
		end

		local has_qualifiers = item.q and item.q[1] or item.qq and item.qq[1] or item.qualifiers and item.qualifiers[1]
			or item.a and item.a[1] or item.aa and item.aa[1]
		local has_gloss_or_pos = item.gloss or item.pos
		if has_qualifiers or has_gloss_or_pos then
			-- FIXME: 현재 설명자 및 품사 (그 순서)를 일반 왼쪽 설명자의 끝에 추가합니다.
			-- 다른 작업을 수행해야 할까요?
			local q = item.q
			if has_gloss_or_pos then
				q = mw.clone(item.q) or {}
				if item.gloss then
					local m_qualifier = require(qualifier_module)
					insert(q, m_qualifier.wrap_qualifier_css("“", "quote") .. item.gloss ..
						m_qualifier.wrap_qualifier_css("”", "quote"))
				end
				if item.pos then
					-- FIXME: [[Module:headword/data]] 또는 유사한 곳에서 별칭 확장을 고려하십시오.
					insert(q, item.pos)
				end
			end

			bit = require("Module:pron qualifier").format_qualifiers {
				lang = lang,
				text = bit,
				q = q,
				qq = item.qq,
				qualifiers = item.qualifiers,
				a = item.a,
				aa = item.aa,
			}
		end

		if item.note then
			-- 2024-06-15에 지원 중단됨.
			error("`.note`에 대한 지원이 중단되었습니다; `.refs`로 전환하십시오 (목록이어야 함)")
		end
		if item.refs then
			local refspecs = item.refs
			if #refspecs > 0 then
				bit = bit .. require(references_module).format_references(refspecs)
			end
		end

		bit = (item.separator or (i == 1 and "" or separator)) .. bit

		insert(bits, bit)

		--[=[	[[Special:WhatLinksHere/Wiktionary:Tracking/IPA/syntax-error]]
				음절 구분 기호 또는 강세 기호 뒤에 길이 또는 중첩 기호가 나타나면 안 됩니다.	]=]

		-- 다음 패턴 매치의 특성상 결합된 '/.../ [...]' 사양을 처리할 필요가 없습니다.
		if match(item.pron, "[.\203][\136\140]?\203[\144\145]") then -- [.ˈˌ][ːˑ]
			track("syntax-error")
		end

		if lang then
			-- 언어의 이중모음이 [[Module:syllables]]에 나열된 경우, 음절 수를 추가합니다.
			-- 용어에 공백, 연음 기호 (‿)가 없거나 메인 네임스페이스에 있지 않은 경우에는 적용하지 않습니다.
			if not no_count and namespace == 0 then
				m_syllables = m_syllables or require(syllables_module)
				local langcode = lang:getCode()
				if m_data.langs_to_generate_syllable_count_categories[langcode] then
					local phonemic, phonetic = split_phonemic_phonetic(item.pron)
					local use_it
					if not phonetic then -- 결합된 '/.../ [...]' 발음이 아님
						local repr = determine_repr(phonemic)
						if m_data.langs_to_use_phonetic_notation[langcode] then
							use_it = repr == "phonetic" and phonemic or nil
						else
							use_it = repr == "phonemic" and phonemic or nil
						end
					elseif repr == "phonetic" then
						use_it = phonetic
					elseif repr == "phonemic" then
						use_it = phonemic
					end
					-- 참고: find를 사용한 두 번의 일반 패턴이 [ ‿]로 umatch를 사용하는 것보다 훨씬 빠릅니다.
					if use_it and not (find(use_it, " ") or find(use_it, "‿")) then
						local syllable_count = m_syllables.getVowels(use_it, lang)
						if syllable_count then
							insert(categories, (lang:getCanonicalName() or lang:getCanonicalName2()) .. " " .. syllable_count ..
								"음절 낱말")
						end
					end
				end
			end

			-- hasInvalidSeparators()의 특성상 결합된 '/.../ [...]' 사양을 처리할 필요가 없습니다.
			if lang:getCode() == "en" and hasInvalidSeparators(item.pron) then
				insert(categories, "영어 .ˈ 또는 .ˌ를 사용하는 IPA")
			end
		end
	end

	return process_maybe_split_categories(split_output, categories, concat(bits), lang)
end

--[=[
단일 IPA 발음을 형식화합니다. 이는 {/.../ [...]}와 같은 결합 사양일 수 없습니다. 이는 {format_IPA()}에서 추출되어 후자가
결합 사양을 처리할 수 있도록 합니다. 이는 {format_IPA()}와 유사하게 작동하지만, 사전 생성된 {err} (오류 메시지용) 및
{categories} 목록을 전달해야 하며, 생성된 오류 메시지와 범주를 해당 목록에 추가합니다. 단일 값이 반환되며,
이는 일반적으로 전달된 발음과 동일하지만, 유효하지 않은 문자가 빨간색으로 표시되도록 HTML이 추가될 수 있습니다.
]=]
local function format_one_IPA(lang, pron, err, categories)
	-- 위키링크를 제거하여 위키링크 괄호가 음성적 전사로 오해되지 않도록 합니다.
	local without_links = gsub(pron, "%[%[[^|%]]+|([^%]]+)%]%]", "%1")
	without_links = gsub(without_links, "%[%[[^%]]+%]%]", "%1")

	-- 이는 음운적 또는 음성적 전사인지 감지합니다.
	local repr, reconstructed = determine_repr(without_links)

	if reconstructed then
		pron = sub(pron, 2)
		without_links = sub(without_links, 2)
	end

	-- 유효한 경우 표현 마크를 제거합니다.
	if repr == "phonemic" then
		pron = usub(pron, 2, -2)
		without_links = usub(without_links, 2, -2)
	elseif repr == "phonetic" then
		pron = usub(pron, 2, -2)
		without_links = usub(without_links, 2, -2)
	elseif repr == "orthographic" then
		pron = usub(pron, 2, -2)
		without_links = usub(without_links, 2, -2)
	elseif repr == "rhyme" then
		pron = usub(pron, 2)
		without_links = usub(without_links, 2)
	else
		insert(categories, "유효하지 않은 표현 마크를 가진 IPA 발음")
		-- insert(err, "유효하지 않은 표현 마크")
		-- 발음 페이지를 미리볼 때 성가시므로 제거됨.
	end

	if pron == "" then
		insert(categories, "발음이 없는 IPA 발음")
	end

	-- 구식 및 비표준 기호 검사
	for i, symbol in ipairs(m_data.nonstandard) do
		local result
		for nonstandard in gmatch(pron, symbol) do
			if not result then
				result = {}
			end
			insert(result, nonstandard)
			insert(categories,
				{cat = "구식 또는 비표준 문자가 포함된 IPA 발음", sort_key = nonstandard}
			)
		end

		if result then
			insert(err, "구식 또는 비표준 문자 (" .. concat(result) .. ")")
			break
		end
	end

	--[[ 다음을 제거한 후 유효하지 않은 기호 검사:
			1. wikilink (위에서 처리됨)
			2. 쌍으로 된 HTML 태그
			3. 볼드체
			4. 이탤릭체
			5. 공백에 대한 HTML 엔터티
			6. 전사 시작 부분의 별표
			7. 쉼표 뒤의 공백 문자
			8. 상위 첨자가 괄호로 둘러싸인 경우		]]
	local found_HTML
	local result = gsub(without_links, "<(%a+)[^>]*>([^<]+)</%1>",
		function(tagName, content)
			found_HTML = true
			return content
		end)
	result = gsub(result, "'''([^']*)'''", "%1")
	result = gsub(result, "''([^']*)''", "%1")
	result = gsub(result, "&[^;]+;", "") -- 이는 유효하지 않은 문자 엔터티도 잡아낼 수 있습니다.
	result = gsub(result, "^%*", "")
	result = ugsub(result, ",%s+", "")

	-- VS15
	local vs15_class = "[" .. m_symbols.add_vs15 .. "]"
	if umatch(pron, vs15_class) then
		local vs15 = u(0xFE0E)
		if find(result, vs15) then
			result = gsub(result, vs15, "")
			pron = gsub(pron, vs15, "")
		end
		pron = ugsub(pron, "(" .. vs15_class .. ")", "%1" .. vs15)
	end

	if result ~= "" then
		local suggestions = {}
		for k, v in pairs(m_symbols.invalid) do
			if find(result, k, 1, true) then
				insert(suggestions, k .. "를 " .. v .. "로 교체")
			end
		end
		if suggestions[1] then
			suggestions = listToText(suggestions)
			if is_content_page then
				error("유효하지 않은 IPA: " .. suggestions .. "로 교체")
			else
				insert(err, suggestions .. "로 교체")
			end
		end
		result = ugsub(result, "⁽[".. m_symbols.superscripts .. "]+⁾", "")
		local per_lang_valid
		if lang then
			per_lang_valid = m_symbols.per_lang_valid[lang:getCode()]
		end
		per_lang_valid = per_lang_valid or ""
		result = ugsub(result, "[" .. m_symbols.valid .. per_lang_valid .. "]", "")
		if result ~= "" then
			local category = "유효하지 않은 IPA 문자가 포함된 IPA 발음"
			if not is_content_page then
				category = category .. "/non_mainspace"
			end
			insert(categories, category)
			insert(err, "유효하지 않은 IPA 문자 (" .. result .. ")")
		end
	end

	if found_HTML then
		insert(categories, "쌍으로 된 HTML 태그가 포함된 IPA 발음")
	end

	if repr == "phonemic" or repr == "rhyme" then
		if lang and m_data.phonemes[lang:getCode()] then
			local valid_phonemes = m_data.phonemes[lang:getCode()]
			local rest = pron
			local phonemes = {}

			while #rest > 0 do
				local longestmatch, longestmatch_len = "", 0

				local rest_init = sub(rest, 1, 1)
				if rest_init == "(" or rest_init == ")" then
					longestmatch = rest_init
					longestmatch_len = 1
				else
					for _, phoneme in ipairs(valid_phonemes) do
						local phoneme_len = len(phoneme)
						if phoneme_len > longestmatch_len and usub(rest, 1, phoneme_len) == phoneme then
							longestmatch = phoneme
							longestmatch_len = len(longestmatch)
						end
					end
				end

				if longestmatch_len > 0 then
					insert(phonemes, longestmatch)
					rest = usub(rest, longestmatch_len + 1)
				else
					local phoneme = usub(rest, 1, 1)
					insert(phonemes, "<span style=\"color: red\">" .. phoneme .. "</span>")
					rest = usub(rest, 2)
					insert(categories, "유효하지 않은 음소가 포함된 IPA 발음/" .. lang:getCode())
					track("유효하지 않은 음소/" .. phoneme)
				end
			end

			pron = concat(phonemes)
		end

		if repr == "phonemic" then
			pron = "/" .. pron .. "/"
		else
			pron = "-" .. pron
		end
	elseif repr == "phonetic" then
		pron = "[" .. pron .. "]"
	elseif repr == "orthographic" then
		pron = "⟨" .. pron .. "⟩"
	end

	if reconstructed then
		pron = "*" .. pron
	end

	return pron
end

--[==[
단일 IPA 발음을 포맷합니다. 이는 발음을 적절한 CSS 클래스로 래핑하고 필요한 경우 정리 범주와 오류 메시지를 추가합니다.
발음 `pron`은 음운적 (/{.../}), 음성적 ({[...]}), 철자적 ({⟨...⟩}), 라임 (하이픈으로 시작) 또는 결합된
음운적/음성적 사양 ({/.../ [...]})이어야 합니다. `lang`은 발음의 언어를 나타내며 {nil}일 수 있습니다.
{nil}이 아닌 경우, 지정된 언어에 [[Module:IPA/data]]에 허용되는 음소에 대한 데이터가 있는 경우, 페이지가 정리 범주에
추가되고 출력된 발음 옆에 오류 메시지가 표시됩니다. {lang}은 추가된 정리 범주의 정렬 키 처리도 결정합니다.
`split_output`이 지정되지 않은 경우 반환 값은 형식화된 발음, 오류 메시지 및 형식화된 정리 범주의 연결입니다.
그렇지 않으면 세 값이 반환됩니다: 형식화된 발음, 정리 범주 및 연결된 오류 메시지. `split_output`이 {"raw"}인
경우, 정리 범주는 목록 형식으로 반환되며, 목록 요소는 [[Module:utilities]]의 {format_categories()}에 전달할 수
있는 범주 문자열과 범주 객체의 조합입니다. `split_output`이 {nil}이 아닌 경우, 정리 범주는 미리 형식화된 연결
문자열로 반환됩니다.
]==]
function export.format_IPA(lang, pron, split_output)
	local err = {}
	local categories = {}

	-- `pron`에 ref 태그가 포함되지 않아야 합니다.
	if match(pron, "\127'\"`UNIQ%-%-ref%-[%dA-F]+%-QINU`\"'\127") then
		error("발음 매개변수 내에 <ref> 태그가 발견되었습니다.")
	end

	if not lang then
		track("format-nolang")
	end

	local phonemic, phonetic = split_phonemic_phonetic(pron)
	pron = format_one_IPA(lang, phonemic, err, categories)
	if phonetic then
		phonetic = format_one_IPA(lang, phonetic, err, categories)
		pron = pron .. " " .. phonetic
	end

	if err[1] then
		err = '<span class="previewonly error" style="font-size: small;>&#32;' .. concat(err, ", ") .. "</span>"
	else
		err = ""
	end

	return process_maybe_split_categories(split_output, categories, '<span class="IPA">' .. pron .. "</span>", lang,
		err)
end

--[==[
enPR 발음 라인을 포맷합니다. 이는 {{tl|enPR}}처럼 "enPR:" (링크는 [[부록:영어 발음]])로 시작하여
하나 이상의 형식화된, 쉼표로 구분된 enPR 발음이 뒤따릅니다. 발음은 {{cd|AHD}} 및 {{cd|enPR}} CSS 클래스로
래핑되고 모든 왼쪽 및 오른쪽 일반 설명자 및 악센트 설명자를 추가하여 형식화됩니다. 또한 전체 결과는
모든 전체 왼쪽 및 오른쪽 일반 설명자 및 악센트 설명자로 래핑됩니다. 단일 매개변수 `data`는 다음 속성을
포함하는 객체입니다:
* `items`는 enPR 발음 목록으로, 각 항목은 다음 속성을 가진 객체입니다:
** `pron`: enPR 발음;
** `q`: {nil} 또는 형식화된 발음 앞에 표시할 왼쪽 설명자 목록 ({{tl|q}}처럼);
** `qq`: {nil} 또는 형식화된 발음 뒤에 표시할 오른쪽 설명자 목록;
** `a`: {nil} 또는 형식화된 발음 앞에 표시할 왼쪽 악센트 설명자 목록 ({{tl|a}}처럼);
** `aa`: {nil} 또는 형식화된 발음 뒤에 표시할 오른쪽 악센트 설명자 목록.
* `q`: {nil} 또는 형식화된 발음 및 {"enPR:"} 앞에 표시할 왼쪽 설명자 목록 ({{tl|q}}처럼);
* `qq`: {nil} 또는 모든 형식화된 발음 뒤에 표시할 오른쪽 설명자 목록;
* `a`: {nil} 또는 형식화된 발음 및 {"enPR:"} 앞에 표시할 왼쪽 악센트 설명자 목록 ({{tl|a}}처럼);
* `aa`: {nil} 또는 모든 형식화된 발음 뒤에 표시할 오른쪽 악센트 설명자 목록.
]==]
function export.format_enPR_full(data)
	local prefix = "[[부록:영어 발음|enPR]]: "
	local lang = require("Module:languages").getByCode("en")
	local parts = {}

	for _, item in ipairs(data.items) do
		local part = '<span class="AHD enPR">' .. item.pron .. "</span>"

		if item.q and item.q[1] or item.qq and item.qq[1] or item.a and item.a[1] or item.aa and item.aa[1] then
			part = require("Module:pron qualifier").format_qualifiers {
				lang = lang,
				text = part,
				q = item.q,
				qq = item.qq,
				a = item.a,
				aa = item.aa,
			}
		end
		insert(parts, part)
	end

	local prontext = prefix .. concat(parts, ", ")
	if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
		prontext = require(pron_qualifier_module).format_qualifiers {
			lang = lang,
			text = prontext,
			q = data.q,
			qq = data.qq,
			a = data.a,
			aa = data.aa,
		}
	end

	return prontext
end

return export