package de.arxsilex.maptools.converter.desktop

import androidx.compose.runtime.staticCompositionLocalOf
import java.nio.charset.StandardCharsets
import java.util.Locale

internal enum class AppLanguage(
    val tag: String,
    val nativeName: String,
) {
    ENGLISH("en-GB", "English"),
    GERMAN("de", "Deutsch"),
    FRENCH("fr", "Français"),
    SPANISH("es", "Español"),
    ITALIAN("it", "Italiano"),
    PORTUGUESE("pt", "Português"),
    DUTCH("nl", "Nederlands"),
    POLISH("pl", "Polski"),
    CZECH("cs", "Čeština"),
    TURKISH("tr", "Türkçe"),
    UKRAINIAN("uk", "Українська"),
    JAPANESE("ja", "日本語"),
    KOREAN("ko", "한국어"),
    CHINESE_SIMPLIFIED("zh-Hans", "简体中文"),
    RUSSIAN("ru", "Русский"),
    ROMANIAN("ro", "Română"),
    SWEDISH("sv", "Svenska");

    val locale: Locale get() = Locale.forLanguageTag(tag)

    companion object {
        fun fromTag(tag: String?): AppLanguage? {
            val normalized = tag?.replace('_', '-') ?: return null
            return entries.firstOrNull { it.tag.equals(normalized, ignoreCase = true) }
                ?: entries.firstOrNull {
                    it.locale.language.equals(Locale.forLanguageTag(normalized).language, ignoreCase = true)
                }
        }

        fun systemDefault(locale: Locale = Locale.getDefault()): AppLanguage =
            fromTag(locale.toLanguageTag()) ?: ENGLISH
    }
}

internal class AppStrings internal constructor(
    val language: AppLanguage,
    private val values: Map<String, String>,
) {
    operator fun get(key: String): String = values[key] ?: LocalizationCatalog.englishValue(key)

    operator fun invoke(key: String, vararg arguments: Any?): String = arguments.foldIndexed(get(key)) { index, text, value ->
        text.replace("{$index}", value.toString())
    }
}

internal object LocalizationCatalog {
    private const val RESOURCE = "/i18n/translations.tsv"
    private val languageColumns = AppLanguage.entries.map(AppLanguage::tag)
    private val translations: Map<AppLanguage, Map<String, String>> by lazy(::load)

    fun strings(language: AppLanguage): AppStrings = AppStrings(language, translations.getValue(language))

    fun englishValue(key: String): String = translations.getValue(AppLanguage.ENGLISH)[key] ?: "[$key]"

    internal fun keys(language: AppLanguage): Set<String> = translations.getValue(language).keys

    private fun load(): Map<AppLanguage, Map<String, String>> {
        val stream = requireNotNull(LocalizationCatalog::class.java.getResourceAsStream(RESOURCE)) {
            "Missing localization resource $RESOURCE"
        }
        val rows = stream.bufferedReader(StandardCharsets.UTF_8).use { it.readLines() }
            .filterNot { it.isBlank() || it.startsWith('#') }
        require(rows.isNotEmpty()) { "Localization resource is empty" }
        val header = rows.first().split('\t')
        require(header.first() == "key") { "First localization column must be 'key'" }
        require(header.drop(1) == languageColumns) {
            "Localization languages do not match AppLanguage: ${header.drop(1)}"
        }
        val maps = AppLanguage.entries.associateWith { linkedMapOf<String, String>() }
        rows.drop(1).forEachIndexed { rowIndex, row ->
            val columns = row.split('\t')
            require(columns.size == header.size) {
                "Localization row ${rowIndex + 2} has ${columns.size} columns; expected ${header.size}"
            }
            val key = columns.first().trim()
            require(key.isNotEmpty()) { "Localization row ${rowIndex + 2} has an empty key" }
            AppLanguage.entries.forEachIndexed { languageIndex, language ->
                val value = columns[languageIndex + 1].replace("\\n", "\n")
                require(value.isNotBlank()) { "Missing $key translation for ${language.tag}" }
                require(maps.getValue(language).put(key, value) == null) { "Duplicate localization key $key" }
            }
        }
        return maps
    }
}

internal val LocalAppStrings = staticCompositionLocalOf { LocalizationCatalog.strings(AppLanguage.ENGLISH) }
