@file:Suppress("HardCodedStringLiteral")

package eu.gaudian.translator.model.grammar

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

/**
 * Comprehensive JSON parser for local dictionary entries.
 * * Enhanced to support detailed phonetics, homophones, form variations,
 * and raw tags for granular linguistic data.
 * * NOW SUPPORTS: Structured Verb Objects (e.g. German "forms": { "present": [...] })
 */
object DictionaryJsonParser {

    private val json = Json { ignoreUnknownKeys = true }

    /**
     * Parse raw JSON string into a structured DictionaryEntryData object.
     */
    fun parseJson(jsonString: String): DictionaryEntryData? {
        val root: JsonElement = try {
            json.parseToJsonElement(jsonString)
        } catch (_: Exception) {
            return null
        }

        val obj = root as? JsonObject ?: return null

        return DictionaryEntryData(
            translations = parseTranslations(obj),
            relations = parseRelations(obj),
            phonetics = parsePhonetics(obj),
            hyphenation = parseHyphenation(obj),
            etymology = parseEtymology(obj),
            senses = parseSenses(obj),
            grammaticalFeatures = parseGrammaticalFeatures(obj),
            grammaticalProperties = parseGrammaticalProperties(obj),
            pronunciation = parsePronunciation(obj),
            inflections = parseInflections(obj),
            forms = parseForms(obj) // Updated to handle Object structures
        )
    }

    private fun parseTranslations(obj: JsonObject): List<TranslationData> {
        val translationsElement = obj["translations"]
        val array = translationsElement as? JsonArray ?: return emptyList()

        return array.mapNotNull { element ->
            val o = element.jsonObject
            val langCode = o["lang_code"]?.jsonPrimitive?.contentOrNull
                ?: o["language_code"]?.jsonPrimitive?.contentOrNull
            val word = o["word"]?.jsonPrimitive?.contentOrNull

            if (langCode.isNullOrBlank() || word.isNullOrBlank()) {
                return@mapNotNull null
            }

            val sense = o["sense"]?.jsonPrimitive?.contentOrNull
                ?: o["sense_index"]?.jsonPrimitive?.contentOrNull
            val tags = (o["tags"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            TranslationData(
                languageCode = langCode,
                word = word,
                sense = sense,
                tags = tags
            )
        }
    }

    private fun parseRelations(obj: JsonObject): Map<String, List<RelationData>> {
        val relationsElement = obj["relations"] as? JsonObject ?: return emptyMap()

        val result = mutableMapOf<String, List<RelationData>>()
        for ((relationType, value) in relationsElement) {
            val array = value as? JsonArray ?: continue
            val relations = array.mapNotNull { element ->
                val o = element.jsonObject
                val word = o["word"]?.jsonPrimitive?.contentOrNull
                if (word.isNullOrBlank()) return@mapNotNull null

                val senseIndex = o["sense_index"]?.jsonPrimitive?.contentOrNull

                // Parse raw_tags
                val rawTags = (o["raw_tags"] as? JsonArray)
                    ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                    ?: emptyList()

                RelationData(
                    word = word,
                    senseIndex = senseIndex,
                    rawTags = rawTags
                )
            }
            result[relationType] = relations
        }
        return result
    }

    private fun parsePhonetics(obj: JsonObject): PhoneticsData? {
        val phoneticsElement = obj["phonetics"] as? JsonObject ?: return null

        // standard IPA list
        val ipaList = (phoneticsElement["ipa"] as? JsonArray)
            ?.mapNotNull { it.jsonPrimitive.contentOrNull?.replace(Regex("[\\[\\]]"), "") }
            ?: emptyList()

        // homophones
        val homophones = (phoneticsElement["homophones"] as? JsonArray)
            ?.mapNotNull { it.jsonPrimitive.contentOrNull }
            ?: emptyList()

        // IPA variations (e.g. regional pronunciations)
        val variationsArray = phoneticsElement["ipa_variations"] as? JsonArray
        val variations = variationsArray?.mapNotNull { element ->
            val vObj = element.jsonObject
            val ipa = vObj["ipa"]?.jsonPrimitive?.contentOrNull ?: return@mapNotNull null
            val rawTags = (vObj["raw_tags"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            IpaVariationData(ipa = ipa, rawTags = rawTags)
        } ?: emptyList()

        return PhoneticsData(
            ipa = ipaList,
            homophones = homophones,
            variations = variations
        )
    }

    private fun parseHyphenation(obj: JsonObject): List<String> {
        val hyphenationElement = obj["hyphenation"] as? JsonArray ?: return emptyList()
        return hyphenationElement.mapNotNull { it.jsonPrimitive.contentOrNull }
    }

    private fun parseEtymology(obj: JsonObject): EtymologyData {
        val element = obj["etymology"] ?: return EtymologyData(texts = emptyList())
        val texts = when (element) {
            is JsonArray -> element.mapNotNull { it.jsonPrimitive.contentOrNull }
            is JsonObject -> {
                val array = element["texts"] as? JsonArray
                if (array != null) {
                    array.mapNotNull { it.jsonPrimitive.contentOrNull }
                } else {
                    val singleText = element["text"]?.jsonPrimitive?.contentOrNull
                    if (singleText != null) listOf(singleText) else emptyList()
                }
            }
            else -> emptyList()
        }
        return EtymologyData(texts = texts)
    }

    private fun parseSenses(obj: JsonObject): List<SenseData> {
        val sensesElement = obj["senses"] as? JsonArray ?: return emptyList()

        return sensesElement.mapNotNull { element ->
            val senseObj = element.jsonObject

            // Handle both "gloss" (string) and "glosses" (array) formats
            val glosses = when {
                senseObj["glosses"] is JsonArray -> {
                    (senseObj["glosses"] as? JsonArray)
                        ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                        ?: emptyList()
                }
                !senseObj["gloss"]?.jsonPrimitive?.contentOrNull.isNullOrBlank() -> {
                    listOf(senseObj["gloss"]!!.jsonPrimitive.content)
                }
                else -> emptyList()
            }

            if (glosses.isEmpty()) return@mapNotNull null

            val topics = (senseObj["topics"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            val examples = (senseObj["examples"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            val tags = (senseObj["tags"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            // Capture raw_tags
            val rawTags = (senseObj["raw_tags"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            val categories = (senseObj["categories"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            SenseData(
                glosses = glosses,
                topics = topics,
                examples = examples,
                tags = tags,
                rawTags = rawTags,
                categories = categories
            )
        }
    }

    private fun parseGrammaticalFeatures(obj: JsonObject): GrammaticalFeaturesData? {
        val featuresElement = obj["grammatical_features"] as? JsonObject ?: return null
        val tags = (featuresElement["tags"] as? JsonArray)
            ?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList()
        val gender = featuresElement["gender"]?.jsonPrimitive?.contentOrNull
        val number = featuresElement["number"]?.jsonPrimitive?.contentOrNull
        return GrammaticalFeaturesData(tags = tags, gender = gender, number = number)
    }

    private fun parseGrammaticalProperties(obj: JsonObject): GrammaticalPropertiesData? {
        val propsElement = obj["grammatical_properties"] as? JsonObject ?: return null
        val otherTags = (propsElement["other_tags"] as? JsonArray)
            ?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList()
        return GrammaticalPropertiesData(otherTags = otherTags)
    }

    private fun parsePronunciation(obj: JsonObject): List<PronunciationData> {
        val pronunciationElement = obj["pronunciation"] as? JsonArray ?: return emptyList()
        return pronunciationElement.mapNotNull { element ->
            val pronObj = element.jsonObject
            val ipa = pronObj["ipa"]?.jsonPrimitive?.contentOrNull
            val rhymes = pronObj["rhymes"]?.jsonPrimitive?.contentOrNull
            if (ipa.isNullOrBlank()) return@mapNotNull null
            PronunciationData(ipa = ipa, rhymes = rhymes)
        }
    }

    private fun parseInflections(obj: JsonObject): List<InflectionData> {
        val inflectionElement = obj["inflections"] as? JsonArray ?: return emptyList()
        return inflectionElement.mapNotNull { element ->
            val infObj = element.jsonObject
            val form = infObj["form"]?.jsonPrimitive?.contentOrNull ?: return@mapNotNull null
            val grammaticalFeatures = (infObj["grammatical_features"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList()
            InflectionData(form = form, grammaticalFeatures = grammaticalFeatures)
        }
    }

    /**
     * Parse forms array from JSON.
     * Handles BOTH standard Arrays and structured Objects (common in German verbs).
     */
    private fun parseForms(obj: JsonObject): List<FormData> {
        val formsElement = obj["forms"] ?: return emptyList()

        return when (formsElement) {
            is JsonArray -> parseFormsArray(formsElement)
            is JsonObject -> parseFormsObject(formsElement)
            else -> emptyList()
        }
    }

    /**
     * Case 1: Standard Array of Form Objects
     */
    private fun parseFormsArray(array: JsonArray): List<FormData> {
        return array.mapNotNull { element ->
            val formObj = element.jsonObject
            val form = formObj["form"]?.jsonPrimitive?.contentOrNull ?: return@mapNotNull null

            val tags = (formObj["tags"] as? JsonArray)
                ?.mapNotNull { it.jsonPrimitive.contentOrNull }
                ?: emptyList()

            // Capture IPAs specific to this form (often escaped like "\\ʃo\\")
            val ipas = (formObj["ipas"] as? JsonArray)
                ?.mapNotNull {
                    it.jsonPrimitive.contentOrNull?.replace("\\", "") // cleanup escapes
                }
                ?: emptyList()

            FormData(
                form = form,
                tags = tags,
                ipas = ipas
            )
        }
    }

    /**
     * Case 2: Structured Object (German Verbs in "laufen" style)
     * Flattens the object keys (present, past, etc.) into FormData objects tagged with that key.
     */
    private fun parseFormsObject(obj: JsonObject): List<FormData> {
        val result = mutableListOf<FormData>()

        // 1. Handle Array Tenses (e.g. "present": ["laufe", "läufst"...])
        val tenseKeys = listOf(
            "present", "past", "future",
            "subjunctive_i", "subjunctive_ii",
            "imperative", "conditional"
        )

        tenseKeys.forEach { key ->
            val array = obj[key] as? JsonArray
            array?.forEach { formElement ->
                val form = formElement.jsonPrimitive.contentOrNull
                if (!form.isNullOrBlank()) {
                    // We tag it with the key (e.g., "present") so the Parser can find it.
                    // The UnifiedMorphologyParser relies on list order for persons (1st->3rd),
                    // so we simply add them in order.
                    result.add(FormData(
                        form = form,
                        tags = listOf(key),
                        ipas = emptyList()
                    ))
                }
            }
        }

        // 2. Handle Single String Properties (e.g. "auxiliary": "sein")
        val singleKeys = listOf("auxiliary", "infinitive", "participle_perfect", "participle_present")

        singleKeys.forEach { key ->
            val value = obj[key]?.jsonPrimitive?.contentOrNull
            if (!value.isNullOrBlank()) {
                result.add(FormData(
                    form = value,
                    tags = listOf(key),
                    ipas = emptyList()
                ))
            }
        }

        return result
    }
}

// ---------------------------------------------------------------------------
// Data Classes
// ---------------------------------------------------------------------------

data class DictionaryEntryData(
    val translations: List<TranslationData>,
    val relations: Map<String, List<RelationData>>,
    val phonetics: PhoneticsData?,
    val hyphenation: List<String>,
    val etymology: EtymologyData,
    val senses: List<SenseData>,
    val grammaticalFeatures: GrammaticalFeaturesData?,
    val grammaticalProperties: GrammaticalPropertiesData?,
    val pronunciation: List<PronunciationData>,
    val inflections: List<InflectionData>,
    val forms: List<FormData>
) {
    val synonyms: List<RelationData>
        get() = relations["synonyms"] ?: emptyList()

    val hyponyms: List<RelationData>
        get() = relations["hyponyms"] ?: emptyList()

    val allRelatedWords: List<RelationData>
        get() = relations.values.flatten()

    val allTags: List<String>
        get() = (grammaticalFeatures?.tags.orEmpty() + grammaticalProperties?.otherTags.orEmpty()).distinct()

}

data class TranslationData(
    val languageCode: String,
    val word: String,
    val sense: String?,
    val tags: List<String>
)

data class RelationData(
    val word: String,
    val senseIndex: String?,
    val rawTags: List<String> = emptyList()
)

data class EtymologyData(
    val texts: List<String>
)

data class SenseData(
    val glosses: List<String>,
    val topics: List<String> = emptyList(),
    val examples: List<String> = emptyList(),
    val tags: List<String> = emptyList(),
    val rawTags: List<String> = emptyList(),
    val categories: List<String> = emptyList()
) {
    val gloss: String
        get() = glosses.firstOrNull() ?: ""
}

data class GrammaticalPropertiesData(
    val otherTags: List<String>
)

data class GrammaticalFeaturesData(
    val tags: List<String>,
    val gender: String? = null,
    val number: String? = null
)

data class PronunciationData(
    val ipa: String,
    val rhymes: String?
)

data class PhoneticsData(
    val ipa: List<String>,
    val homophones: List<String>,
    val variations: List<IpaVariationData>
)

data class IpaVariationData(
    val ipa: String,
    val rawTags: List<String>
)

data class InflectionData(
    val form: String,
    val grammaticalFeatures: List<String>
)

data class FormData(
    val form: String,
    val tags: List<String>,
    val ipas: List<String>
)