package dev.inmo.tgbotapi.types.message

import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.types.message.textsources.*
import kotlinx.serialization.Serializable

@Serializable
internal data class RawMessageEntity(
    val type: String,
    val offset: Int,
    val length: Int,
    val url: String? = null,
    val user: User? = null,
    val language: String? = null,
    val custom_emoji_id: CustomEmojiId? = null
) {
    internal val range by lazy {
        offset until (offset + length)
    }
}

internal fun RawMessageEntity.asTextSource(
    source: String,
    subParts: List<Pair<Int, TextSource>>
): TextSource {
    val sourceSubstring: String = source.substring(range)
    val subPartsWithRegulars by lazy {
        subParts.map { (it.first - offset) to it.second }.fillWithRegulars(sourceSubstring)
    }
    return when (type) {
        "mention" -> MentionTextSource(sourceSubstring, subPartsWithRegulars)
        "hashtag" -> HashTagTextSource(sourceSubstring, subPartsWithRegulars)
        "cashtag" -> CashTagTextSource(sourceSubstring, subPartsWithRegulars)
        "bot_command" -> BotCommandTextSource(sourceSubstring)
        "url" -> URLTextSource(sourceSubstring)
        "email" -> EMailTextSource(sourceSubstring, subPartsWithRegulars)
        "phone_number" -> PhoneNumberTextSource(sourceSubstring, subPartsWithRegulars)
        "bold" -> BoldTextSource(sourceSubstring, subPartsWithRegulars)
        "italic" -> ItalicTextSource(sourceSubstring, subPartsWithRegulars)
        "code" -> CodeTextSource(sourceSubstring)
        "pre" -> PreTextSource(sourceSubstring, language)
        "text_link" -> TextLinkTextSource(
            sourceSubstring,
            url ?: throw IllegalStateException("URL must not be null for text link")
        )
        "text_mention" -> TextMentionTextSource(
            sourceSubstring,
            user ?: throw IllegalStateException("User must not be null for text mention"),
            subPartsWithRegulars
        )
        "underline" -> UnderlineTextSource(sourceSubstring, subPartsWithRegulars)
        "strikethrough" -> StrikethroughTextSource(sourceSubstring, subPartsWithRegulars)
        "spoiler" -> SpoilerTextSource(sourceSubstring, subPartsWithRegulars)
        "custom_emoji" -> CustomEmojiTextSource(sourceSubstring, custom_emoji_id ?: error("For custom emoji custom_emoji_id should exists"), subPartsWithRegulars)
        else -> RegularTextSource(sourceSubstring)
    }
}

private inline operator fun <T : Comparable<T>> ClosedRange<T>.contains(other: ClosedRange<T>): Boolean {
    return start <= other.start && endInclusive >= other.endInclusive
}

internal fun List<Pair<Int, TextSource>>.fillWithRegulars(source: String): TextSourcesList {
    var index = 0
    val result = mutableListOf<TextSource>()
    for (i in indices) {
        val (offset, textSource) = get(i)
        if (offset - index > 0) {
            result.add(regular(source.substring(index, offset)))
            index = offset
        }
        result.add(textSource)
        index += textSource.source.length
    }

    if (index != source.length) {
        result.add(regular(source.substring(index, source.length)))
    }

    return result
}

private fun createTextSources(
    originalFullString: String,
    entities: RawMessageEntities
): List<Pair<Int, TextSource>> {
    val mutableEntities = entities.toMutableList().apply { sortBy { it.offset } }
    val resultList = mutableListOf<Pair<Int, TextSource>>()

    while (mutableEntities.isNotEmpty()) {
        var parent = mutableEntities.removeFirst()
        val subentities = mutableListOf<RawMessageEntity>()
        val toAddCutted = mutableListOf<RawMessageEntity>()
        while (mutableEntities.isNotEmpty()) {
            val potentialParent = mutableEntities.first()
            when {
                potentialParent.range.first > parent.range.last -> break
                potentialParent.range in parent.range -> {
                    subentities.add(potentialParent)
                }
                potentialParent.offset == parent.offset && potentialParent.length > parent.length -> {
                    subentities.add(parent)
                    parent = potentialParent
                }
                else -> { // need to cut
                    toAddCutted.add(potentialParent)
                }
            }
            mutableEntities.remove(potentialParent)
        }
        val subtextSources = if (subentities.isNotEmpty()) {
            mutableEntities.removeAll(subentities)
            if (toAddCutted.isNotEmpty()) {
                val borderIndex = parent.range.last + 1
                mutableEntities.addAll(
                    0,
                    toAddCutted.map {
                        val firstLength = borderIndex - it.offset
                        subentities.add(it.copy(length = firstLength))
                        it.copy(
                            offset = borderIndex,
                            length = it.length - firstLength
                        )
                    }
                )
            }
            createTextSources(originalFullString, subentities)
        } else {
            emptyList()
        }
        resultList.add(
            parent.offset to parent.asTextSource(
                originalFullString,
                subtextSources
            )
        )
    }

    return resultList
}

internal fun TextSource.toRawMessageEntities(offset: Int = 0): List<RawMessageEntity> {
    val source = source
    val length = source.length
    return listOfNotNull(
        when (this) {
            is MentionTextSource -> RawMessageEntity("mention", offset, length)
            is HashTagTextSource -> RawMessageEntity("hashtag", offset, length)
            is CashTagTextSource -> RawMessageEntity("cashtag", offset, length)
            is BotCommandTextSource -> RawMessageEntity("bot_command", offset, length)
            is URLTextSource -> RawMessageEntity("url", offset, length)
            is EMailTextSource -> RawMessageEntity("email", offset, length)
            is PhoneNumberTextSource -> RawMessageEntity("phone_number", offset, length)
            is BoldTextSource -> RawMessageEntity("bold", offset, length)
            is ItalicTextSource -> RawMessageEntity("italic", offset, length)
            is CodeTextSource -> RawMessageEntity("code", offset, length)
            is PreTextSource -> RawMessageEntity("pre", offset, length, language = language)
            is TextLinkTextSource -> RawMessageEntity("text_link", offset, length, url)
            is TextMentionTextSource -> RawMessageEntity("text_mention", offset, length, user = user)
            is UnderlineTextSource -> RawMessageEntity("underline", offset, length)
            is StrikethroughTextSource -> RawMessageEntity("strikethrough", offset, length)
            is SpoilerTextSource -> RawMessageEntity("spoiler", offset, length)
            is CustomEmojiTextSource -> RawMessageEntity("custom_emoji", offset, length, custom_emoji_id = customEmojiId)
            is RegularTextSource -> null
        }
    ) + if (this is MultilevelTextSource) {
        subsources.toRawMessageEntities(offset)
    } else {
        emptyList()
    }
}


internal fun TextSourcesList.toRawMessageEntities(preOffset: Int = 0): List<RawMessageEntity> {
    var i = preOffset
    return flatMap { textSource ->
        textSource.toRawMessageEntities(i).also {
            i += it.maxByOrNull { it.length }?.length ?: textSource.source.length
        }
    }
}

internal fun TextSourcesList.toRawMessageEntities(): List<RawMessageEntity> = toRawMessageEntities(0)

internal fun RawMessageEntities.asTextSources(sourceString: String): TextSourcesList =
    createTextSources(sourceString, this).fillWithRegulars(sourceString)

internal typealias RawMessageEntities = List<RawMessageEntity>
