package net.sergeych.toread.text

interface HyphenationPlugin {
    val languageTags: Set<String>

    fun hyphenateWord(word: String): String
}

class HyphenationRegistry(
    plugins: List<HyphenationPlugin> = listOf(
        EnglishHyphenationPlugin,
        RussianHyphenationPlugin,
    ),
) {
    private val byLanguage = plugins
        .flatMap { plugin -> plugin.languageTags.map { it.lowercase() to plugin } }
        .toMap()

    fun pluginFor(languageTag: String?): HyphenationPlugin? {
        val normalized = languageTag?.lowercase()?.takeIf { it.isNotBlank() } ?: return null
        return byLanguage[normalized] ?: byLanguage[normalized.substringBefore('-')]
    }

    fun hyphenate(text: String, languageTag: String?): String {
        val plugin = pluginFor(languageTag) ?: return text
        return hyphenate(text, plugin)
    }

    private fun hyphenate(text: String, plugin: HyphenationPlugin): String = buildString(text.length + text.length / 12) {
        var wordStart = -1

        fun flushWord(end: Int) {
            if (wordStart >= 0) {
                append(plugin.hyphenateWord(text.substring(wordStart, end)))
                wordStart = -1
            }
        }

        text.forEachIndexed { index, char ->
            if (char.isLetter()) {
                if (wordStart < 0) wordStart = index
            } else {
                flushWord(index)
                append(char)
            }
        }
        flushWord(text.length)
    }
}

object EnglishHyphenationPlugin : HyphenationPlugin {
    override val languageTags: Set<String> = setOf("en", "eng")

    override fun hyphenateWord(word: String): String {
        if (word.length < 7 || SoftHyphen in word) return word
        val breaks = mutableListOf<Int>()
        for (index in 2 until word.lastIndex - 1) {
            val prev = word[index - 1]
            val current = word[index]
            val next = word[index + 1]
            if (prev.isVowel() && current.isConsonant() && next.isVowel()) breaks += index
            if (current.isConsonant() && next.isConsonant() && word.getOrNull(index + 2)?.isVowel() == true) {
                breaks += index + 1
            }
        }
        return insertBreaks(word, breaks, minPrefix = 3, minSuffix = 3)
    }
}

object RussianHyphenationPlugin : HyphenationPlugin {
    override val languageTags: Set<String> = setOf("ru", "rus")

    override fun hyphenateWord(word: String): String {
        if (word.length < 6 || SoftHyphen in word) return word
        val breaks = mutableListOf<Int>()
        for (index in 2 until word.lastIndex) {
            val prev = word[index - 1]
            val current = word[index]
            val next = word[index + 1]
            if (current.isRussianVowel() && next.isRussianConsonant()) breaks += index + 1
            if (prev.isRussianVowel() && current.isRussianConsonant() && next.isRussianVowel()) breaks += index
        }
        return insertBreaks(word, breaks, minPrefix = 2, minSuffix = 2)
    }
}

const val SoftHyphen: Char = '\u00AD'

private fun insertBreaks(word: String, breaks: List<Int>, minPrefix: Int, minSuffix: Int): String {
    val legalBreaks = breaks.distinct()
        .filter { it >= minPrefix && word.length - it >= minSuffix }
        .toSet()
    if (legalBreaks.isEmpty()) return word

    return buildString(word.length + legalBreaks.size) {
        word.forEachIndexed { index, char ->
            if (index in legalBreaks) append(SoftHyphen)
            append(char)
        }
    }
}

private fun Char.isVowel(): Boolean = lowercaseChar() in "aeiouy"

private fun Char.isConsonant(): Boolean = isLetter() && !isVowel()

private fun Char.isRussianVowel(): Boolean = lowercaseChar() in "аеёиоуыэюя"

private fun Char.isRussianConsonant(): Boolean =
    lowercaseChar() in "бвгджзйклмнпрстфхцчшщ"
